+ thread_bind(master_processor);
+
+ /* Switch to bound processor if not already there */
+ thread_block(THREAD_CONTINUE_NULL);
+}
+
+static void
+sched_vm_group_maintenance(void)
+{
+ uint64_t ctime = mach_absolute_time();
+ uint64_t longtime = ctime - sched_tick_interval;
+ int i;
+ spl_t s;
+ boolean_t high_latency_observed = FALSE;
+ boolean_t runnable_and_not_on_runq_observed = FALSE;
+ boolean_t bind_target_changed = FALSE;
+ processor_t bind_target = PROCESSOR_NULL;
+
+ /* Make sure nobody attempts to add new threads while we are enumerating them */
+ simple_lock(&sched_vm_group_list_lock);
+
+ s = splsched();
+
+ for (i=0; i < sched_vm_group_thread_count; i++) {
+ thread_t thread = sched_vm_group_thread_list[i];
+ assert(thread != THREAD_NULL);
+ thread_lock(thread);
+ if ((thread->state & (TH_RUN|TH_WAIT)) == TH_RUN) {
+ if (thread->runq != PROCESSOR_NULL && thread->last_made_runnable_time < longtime) {
+ high_latency_observed = TRUE;
+ } else if (thread->runq == PROCESSOR_NULL) {
+ /* There are some cases where a thread be transitiong that also fall into this case */
+ runnable_and_not_on_runq_observed = TRUE;
+ }
+ }
+ thread_unlock(thread);
+
+ if (high_latency_observed && runnable_and_not_on_runq_observed) {
+ /* All the things we are looking for are true, stop looking */
+ break;
+ }
+ }
+
+ splx(s);
+
+ if (sched_vm_group_temporarily_unbound) {
+ /* If we turned off binding, make sure everything is OK before rebinding */
+ if (!high_latency_observed) {
+ /* rebind */
+ bind_target_changed = TRUE;
+ bind_target = master_processor;
+ sched_vm_group_temporarily_unbound = FALSE; /* might be reset to TRUE if change cannot be completed */
+ }
+ } else {
+ /*
+ * Check if we're in a bad state, which is defined by high
+ * latency with no core currently executing a thread. If a
+ * single thread is making progress on a CPU, that means the
+ * binding concept to reduce parallelism is working as
+ * designed.
+ */
+ if (high_latency_observed && !runnable_and_not_on_runq_observed) {
+ /* unbind */
+ bind_target_changed = TRUE;
+ bind_target = PROCESSOR_NULL;
+ sched_vm_group_temporarily_unbound = TRUE;
+ }
+ }
+
+ if (bind_target_changed) {
+ s = splsched();
+ for (i=0; i < sched_vm_group_thread_count; i++) {
+ thread_t thread = sched_vm_group_thread_list[i];
+ boolean_t removed;
+ assert(thread != THREAD_NULL);
+
+ thread_lock(thread);
+ removed = thread_run_queue_remove(thread);
+ if (removed || ((thread->state & (TH_RUN | TH_WAIT)) == TH_WAIT)) {
+ thread_bind_internal(thread, bind_target);
+ } else {
+ /*
+ * Thread was in the middle of being context-switched-to,
+ * or was in the process of blocking. To avoid switching the bind
+ * state out mid-flight, defer the change if possible.
+ */
+ if (bind_target == PROCESSOR_NULL) {
+ thread_bind_internal(thread, bind_target);
+ } else {
+ sched_vm_group_temporarily_unbound = TRUE; /* next pass will try again */
+ }
+ }
+
+ if (removed) {
+ thread_run_queue_reinsert(thread, SCHED_PREEMPT | SCHED_TAILQ);
+ }
+ thread_unlock(thread);
+ }
+ splx(s);
+ }
+
+ simple_unlock(&sched_vm_group_list_lock);
+}
+
+/* Invoked prior to idle entry to determine if, on SMT capable processors, an SMT
+ * rebalancing opportunity exists when a core is (instantaneously) idle, but
+ * other SMT-capable cores may be over-committed. TODO: some possible negatives:
+ * IPI thrash if this core does not remain idle following the load balancing ASTs
+ * Idle "thrash", when IPI issue is followed by idle entry/core power down
+ * followed by a wakeup shortly thereafter.
+ */
+
+#if (DEVELOPMENT || DEBUG)
+int sched_smt_balance = 1;
+#endif
+
+#if __SMP__
+/* Invoked with pset locked, returns with pset unlocked */
+static void
+sched_SMT_balance(processor_t cprocessor, processor_set_t cpset) {
+ processor_t ast_processor = NULL;
+
+#if (DEVELOPMENT || DEBUG)
+ if (__improbable(sched_smt_balance == 0))
+ goto smt_balance_exit;
+#endif
+
+ assert(cprocessor == current_processor());
+ if (cprocessor->is_SMT == FALSE)
+ goto smt_balance_exit;
+
+ processor_t sib_processor = cprocessor->processor_secondary ? cprocessor->processor_secondary : cprocessor->processor_primary;
+
+ /* Determine if both this processor and its sibling are idle,
+ * indicating an SMT rebalancing opportunity.
+ */
+ if (sib_processor->state != PROCESSOR_IDLE)
+ goto smt_balance_exit;
+
+ processor_t sprocessor;
+
+ sprocessor = (processor_t)queue_first(&cpset->active_queue);
+
+ while (!queue_end(&cpset->active_queue, (queue_entry_t)sprocessor)) {
+ if ((sprocessor->state == PROCESSOR_RUNNING) &&
+ (sprocessor->processor_primary != sprocessor) &&
+ (sprocessor->processor_primary->state == PROCESSOR_RUNNING) &&
+ (sprocessor->current_pri < BASEPRI_RTQUEUES) &&
+ ((cpset->pending_AST_cpu_mask & (1ULL << sprocessor->cpu_id)) == 0)) {
+ assert(sprocessor != cprocessor);
+ ast_processor = sprocessor;
+ break;
+ }
+ sprocessor = (processor_t)queue_next((queue_entry_t)sprocessor);
+ }
+
+smt_balance_exit:
+ pset_unlock(cpset);
+
+ if (ast_processor) {
+ KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_SCHED, MACH_SCHED_SMT_BALANCE), ast_processor->cpu_id, ast_processor->state, ast_processor->processor_primary->state, 0, 0);
+ cause_ast_check(ast_processor);
+ }
+}
+#endif /* __SMP__ */
+
+/*
+ * thread_select:
+ *
+ * Select a new thread for the current processor to execute.
+ *
+ * May select the current thread, which must be locked.
+ */
+static thread_t
+thread_select(
+ thread_t thread,
+ processor_t processor,
+ ast_t reason)
+{
+ processor_set_t pset = processor->processor_set;
+ thread_t new_thread = THREAD_NULL;
+
+ assert(processor == current_processor());
+ assert((thread->state & (TH_RUN|TH_TERMINATE2)) == TH_RUN);
+
+ do {
+ /*
+ * Update the priority.
+ */
+ if (SCHED(can_update_priority)(thread))
+ SCHED(update_priority)(thread);
+
+ processor->current_pri = thread->sched_pri;
+ processor->current_thmode = thread->sched_mode;
+ processor->current_sfi_class = thread->sfi_class;
+
+ pset_lock(pset);
+
+ assert(processor->state != PROCESSOR_OFF_LINE);
+
+ if (!processor->is_recommended) {
+ /*
+ * The performance controller has provided a hint to not dispatch more threads,
+ * unless they are bound to us (and thus we are the only option
+ */
+ if (!SCHED(processor_bound_count)(processor)) {
+ goto idle;
+ }
+ } else if (processor->processor_primary != processor) {
+ /*
+ * Should this secondary SMT processor attempt to find work? For pset runqueue systems,
+ * we should look for work only under the same conditions that choose_processor()
+ * would have assigned work, which is when all primary processors have been assigned work.
+ *
+ * An exception is that bound threads are dispatched to a processor without going through
+ * choose_processor(), so in those cases we should continue trying to dequeue work.
+ */
+ if (!SCHED(processor_bound_count)(processor) && !queue_empty(&pset->idle_queue) && !rt_runq.count) {
+ goto idle;
+ }
+ }
+
+ rt_lock_lock();
+
+ /*
+ * Test to see if the current thread should continue
+ * to run on this processor. Must not be attempting to wait, and not
+ * bound to a different processor, nor be in the wrong
+ * processor set, nor be forced to context switch by TH_SUSP.
+ *
+ * Note that there are never any RT threads in the regular runqueue.
+ *
+ * This code is very insanely tricky.
+ */
+
+ if (((thread->state & (TH_TERMINATE|TH_IDLE|TH_WAIT|TH_RUN|TH_SUSP)) == TH_RUN) &&
+ (thread->sched_pri >= BASEPRI_RTQUEUES || processor->processor_primary == processor) &&
+ (thread->bound_processor == PROCESSOR_NULL || thread->bound_processor == processor) &&
+ (thread->affinity_set == AFFINITY_SET_NULL || thread->affinity_set->aset_pset == pset)) {
+ /*
+ * RT threads with un-expired quantum stay on processor,
+ * unless there's a valid RT thread with an earlier deadline.
+ */
+ if (thread->sched_pri >= BASEPRI_RTQUEUES && processor->first_timeslice) {
+ if (rt_runq.count > 0) {
+ thread_t next_rt;
+
+ next_rt = (thread_t)queue_first(&rt_runq.queue);
+
+ assert(next_rt->runq == THREAD_ON_RT_RUNQ);
+
+ if (next_rt->realtime.deadline < processor->deadline &&
+ (next_rt->bound_processor == PROCESSOR_NULL ||
+ next_rt->bound_processor == processor)) {
+ /* The next RT thread is better, so pick it off the runqueue. */
+ goto pick_new_rt_thread;
+ }
+ }
+
+ /* This is still the best RT thread to run. */
+ processor->deadline = thread->realtime.deadline;
+
+ rt_lock_unlock();
+ pset_unlock(pset);
+
+ return (thread);
+ }
+
+ if ((rt_runq.count == 0) &&
+ SCHED(processor_queue_has_priority)(processor, thread->sched_pri, TRUE) == FALSE) {
+ /* This thread is still the highest priority runnable (non-idle) thread */
+ processor->deadline = UINT64_MAX;
+
+ rt_lock_unlock();
+ pset_unlock(pset);
+
+ return (thread);
+ }
+ }
+
+ /* OK, so we're not going to run the current thread. Look at the RT queue. */
+ if (rt_runq.count > 0) {
+ thread_t next_rt = (thread_t)queue_first(&rt_runq.queue);
+
+ assert(next_rt->runq == THREAD_ON_RT_RUNQ);
+
+ if (__probable((next_rt->bound_processor == PROCESSOR_NULL ||
+ (next_rt->bound_processor == processor)))) {
+pick_new_rt_thread:
+ new_thread = (thread_t)dequeue_head(&rt_runq.queue);
+
+ new_thread->runq = PROCESSOR_NULL;
+ SCHED_STATS_RUNQ_CHANGE(&rt_runq.runq_stats, rt_runq.count);
+ rt_runq.count--;
+
+ processor->deadline = new_thread->realtime.deadline;
+
+ rt_lock_unlock();
+ pset_unlock(pset);
+
+ return (new_thread);
+ }
+ }
+
+ processor->deadline = UINT64_MAX;
+ rt_lock_unlock();
+
+ /* No RT threads, so let's look at the regular threads. */
+ if ((new_thread = SCHED(choose_thread)(processor, MINPRI, reason)) != THREAD_NULL) {
+ pset_unlock(pset);
+ return (new_thread);
+ }
+
+#if __SMP__
+ if (SCHED(steal_thread_enabled)) {
+ /*
+ * No runnable threads, attempt to steal
+ * from other processors. Returns with pset lock dropped.
+ */
+
+ if ((new_thread = SCHED(steal_thread)(pset)) != THREAD_NULL) {
+ return (new_thread);
+ }
+
+ /*
+ * If other threads have appeared, shortcut
+ * around again.
+ */
+ if (!SCHED(processor_queue_empty)(processor) || rt_runq.count > 0)
+ continue;
+
+ pset_lock(pset);
+ }
+#endif
+
+ idle:
+ /*
+ * Nothing is runnable, so set this processor idle if it
+ * was running.
+ */
+ if (processor->state == PROCESSOR_RUNNING) {
+ remqueue((queue_entry_t)processor);
+ processor->state = PROCESSOR_IDLE;
+
+ if (processor->processor_primary == processor) {
+ enqueue_head(&pset->idle_queue, (queue_entry_t)processor);
+ }
+ else {
+ enqueue_head(&pset->idle_secondary_queue, (queue_entry_t)processor);
+ }
+ }
+
+#if __SMP__
+ /* Invoked with pset locked, returns with pset unlocked */
+ sched_SMT_balance(processor, pset);
+#else
+ pset_unlock(pset);
+#endif
+
+#if CONFIG_SCHED_IDLE_IN_PLACE
+ /*
+ * Choose idle thread if fast idle is not possible.
+ */
+ if (processor->processor_primary != processor)
+ return (processor->idle_thread);
+
+ if ((thread->state & (TH_IDLE|TH_TERMINATE|TH_SUSP)) || !(thread->state & TH_WAIT) || thread->wake_active || thread->sched_pri >= BASEPRI_RTQUEUES)
+ return (processor->idle_thread);
+
+ /*
+ * Perform idling activities directly without a
+ * context switch. Return dispatched thread,
+ * else check again for a runnable thread.
+ */
+ new_thread = thread_select_idle(thread, processor);
+
+#else /* !CONFIG_SCHED_IDLE_IN_PLACE */
+
+ /*
+ * Do a full context switch to idle so that the current
+ * thread can start running on another processor without
+ * waiting for the fast-idled processor to wake up.
+ */
+ new_thread = processor->idle_thread;
+
+#endif /* !CONFIG_SCHED_IDLE_IN_PLACE */
+
+ } while (new_thread == THREAD_NULL);
+
+ return (new_thread);
+}
+
+#if CONFIG_SCHED_IDLE_IN_PLACE
+/*
+ * thread_select_idle:
+ *
+ * Idle the processor using the current thread context.
+ *
+ * Called with thread locked, then dropped and relocked.
+ */
+static thread_t
+thread_select_idle(
+ thread_t thread,
+ processor_t processor)
+{
+ thread_t new_thread;
+ uint64_t arg1, arg2;
+ int urgency;
+
+ if (thread->sched_mode == TH_MODE_TIMESHARE) {
+ if (thread->sched_flags & TH_SFLAG_THROTTLED)
+ sched_background_decr(thread);
+
+ sched_share_decr(thread);
+ }
+ sched_run_decr(thread);
+
+ thread->state |= TH_IDLE;
+ processor->current_pri = IDLEPRI;
+ processor->current_thmode = TH_MODE_NONE;
+ processor->current_sfi_class = SFI_CLASS_KERNEL;
+
+ /* Reload precise timing global policy to thread-local policy */
+ thread->precise_user_kernel_time = use_precise_user_kernel_time(thread);
+
+ thread_unlock(thread);
+
+ /*
+ * Switch execution timing to processor idle thread.
+ */
+ processor->last_dispatch = mach_absolute_time();
+
+#ifdef CONFIG_MACH_APPROXIMATE_TIME
+ commpage_update_mach_approximate_time(processor->last_dispatch);
+#endif
+
+ thread->last_run_time = processor->last_dispatch;
+ thread_timer_event(processor->last_dispatch, &processor->idle_thread->system_timer);
+ PROCESSOR_DATA(processor, kernel_timer) = &processor->idle_thread->system_timer;
+
+ /*
+ * Cancel the quantum timer while idling.
+ */
+ timer_call_cancel(&processor->quantum_timer);
+ processor->first_timeslice = FALSE;
+
+ (*thread->sched_call)(SCHED_CALL_BLOCK, thread);
+
+ thread_tell_urgency(THREAD_URGENCY_NONE, 0, 0, 0, NULL);
+
+ /*
+ * Enable interrupts and perform idling activities. No
+ * preemption due to TH_IDLE being set.
+ */
+ spllo(); new_thread = processor_idle(thread, processor);
+
+ /*
+ * Return at splsched.
+ */
+ (*thread->sched_call)(SCHED_CALL_UNBLOCK, thread);
+
+ thread_lock(thread);
+
+ /*
+ * If awakened, switch to thread timer and start a new quantum.
+ * Otherwise skip; we will context switch to another thread or return here.
+ */
+ if (!(thread->state & TH_WAIT)) {
+ processor->last_dispatch = mach_absolute_time();
+ thread_timer_event(processor->last_dispatch, &thread->system_timer);
+ PROCESSOR_DATA(processor, kernel_timer) = &thread->system_timer;
+
+ thread_quantum_init(thread);
+ processor->quantum_end = processor->last_dispatch + thread->quantum_remaining;
+ timer_call_enter1(&processor->quantum_timer, thread, processor->quantum_end, TIMER_CALL_SYS_CRITICAL | TIMER_CALL_LOCAL);
+ processor->first_timeslice = TRUE;
+
+ thread->computation_epoch = processor->last_dispatch;
+ }
+
+ thread->state &= ~TH_IDLE;
+
+ urgency = thread_get_urgency(thread, &arg1, &arg2);
+
+ thread_tell_urgency(urgency, arg1, arg2, 0, new_thread);
+
+ sched_run_incr(thread);
+ if (thread->sched_mode == TH_MODE_TIMESHARE) {
+ sched_share_incr(thread);
+
+ if (thread->sched_flags & TH_SFLAG_THROTTLED)
+ sched_background_incr(thread);
+ }
+
+ return (new_thread);
+}
+#endif /* CONFIG_SCHED_IDLE_IN_PLACE */
+
+/*
+ * thread_invoke
+ *
+ * Called at splsched with neither thread locked.
+ *
+ * Perform a context switch and start executing the new thread.
+ *
+ * Returns FALSE when the context switch didn't happen.
+ * The reference to the new thread is still consumed.
+ *
+ * "self" is what is currently running on the processor,
+ * "thread" is the new thread to context switch to
+ * (which may be the same thread in some cases)
+ */
+static boolean_t
+thread_invoke(
+ thread_t self,
+ thread_t thread,
+ ast_t reason)
+{
+ if (__improbable(get_preemption_level() != 0)) {
+ int pl = get_preemption_level();
+ panic("thread_invoke: preemption_level %d, possible cause: %s",
+ pl, (pl < 0 ? "unlocking an unlocked mutex or spinlock" :
+ "blocking while holding a spinlock, or within interrupt context"));
+ }
+
+ thread_continue_t continuation = self->continuation;
+ void *parameter = self->parameter;
+ processor_t processor;
+
+ uint64_t ctime = mach_absolute_time();
+
+#ifdef CONFIG_MACH_APPROXIMATE_TIME
+ commpage_update_mach_approximate_time(ctime);
+#endif
+
+#if defined(CONFIG_SCHED_TIMESHARE_CORE)
+ sched_timeshare_consider_maintenance(ctime);
+#endif
+
+ assert(self == current_thread());
+ assert(self->runq == PROCESSOR_NULL);
+ assert((self->state & (TH_RUN|TH_TERMINATE2)) == TH_RUN);
+
+ thread_lock(thread);
+
+ assert((thread->state & (TH_RUN|TH_WAIT|TH_UNINT|TH_TERMINATE|TH_TERMINATE2)) == TH_RUN);
+ assert(thread->bound_processor == PROCESSOR_NULL || thread->bound_processor == current_processor());
+ assert(thread->runq == PROCESSOR_NULL);
+
+ /* Reload precise timing global policy to thread-local policy */
+ thread->precise_user_kernel_time = use_precise_user_kernel_time(thread);
+
+ /* Update SFI class based on other factors */
+ thread->sfi_class = sfi_thread_classify(thread);
+
+ /* Allow realtime threads to hang onto a stack. */
+ if ((self->sched_mode == TH_MODE_REALTIME) && !self->reserved_stack)
+ self->reserved_stack = self->kernel_stack;
+
+ if (continuation != NULL) {
+ if (!thread->kernel_stack) {
+ /*
+ * If we are using a privileged stack,
+ * check to see whether we can exchange it with
+ * that of the other thread.
+ */
+ if (self->kernel_stack == self->reserved_stack && !thread->reserved_stack)
+ goto need_stack;
+
+ /*
+ * Context switch by performing a stack handoff.
+ */
+ continuation = thread->continuation;
+ parameter = thread->parameter;
+
+ processor = current_processor();
+ processor->active_thread = thread;
+ processor->current_pri = thread->sched_pri;
+ processor->current_thmode = thread->sched_mode;
+ processor->current_sfi_class = thread->sfi_class;
+ if (thread->last_processor != processor && thread->last_processor != NULL) {
+ if (thread->last_processor->processor_set != processor->processor_set)
+ thread->ps_switch++;
+ thread->p_switch++;
+ }
+ thread->last_processor = processor;
+ thread->c_switch++;
+ ast_context(thread);
+
+ thread_unlock(thread);
+
+ self->reason = reason;
+
+ processor->last_dispatch = ctime;
+ self->last_run_time = ctime;
+ thread_timer_event(ctime, &thread->system_timer);
+ PROCESSOR_DATA(processor, kernel_timer) = &thread->system_timer;
+
+ /*
+ * Since non-precise user/kernel time doesn't update the state timer
+ * during privilege transitions, synthesize an event now.
+ */
+ if (!thread->precise_user_kernel_time) {
+ timer_switch(PROCESSOR_DATA(processor, current_state),
+ ctime,
+ PROCESSOR_DATA(processor, current_state));
+ }
+
+ KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
+ MACHDBG_CODE(DBG_MACH_SCHED, MACH_STACK_HANDOFF)|DBG_FUNC_NONE,
+ self->reason, (uintptr_t)thread_tid(thread), self->sched_pri, thread->sched_pri, 0);
+
+ if ((thread->chosen_processor != processor) && (thread->chosen_processor != PROCESSOR_NULL)) {
+ SCHED_DEBUG_CHOOSE_PROCESSOR_KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_SCHED, MACH_MOVED)|DBG_FUNC_NONE,
+ (uintptr_t)thread_tid(thread), (uintptr_t)thread->chosen_processor->cpu_id, 0, 0, 0);
+ }
+
+ DTRACE_SCHED2(off__cpu, struct thread *, thread, struct proc *, thread->task->bsd_info);
+
+ SCHED_STATS_CSW(processor, self->reason, self->sched_pri, thread->sched_pri);
+
+ TLOG(1, "thread_invoke: calling stack_handoff\n");
+ stack_handoff(self, thread);
+
+ /* 'self' is now off core */
+ assert(thread == current_thread());
+
+ DTRACE_SCHED(on__cpu);
+
+ thread_dispatch(self, thread);
+
+ thread->continuation = thread->parameter = NULL;
+
+ counter(c_thread_invoke_hits++);
+
+ (void) spllo();
+
+ assert(continuation);
+ call_continuation(continuation, parameter, thread->wait_result);
+ /*NOTREACHED*/
+ }
+ else if (thread == self) {
+ /* same thread but with continuation */
+ ast_context(self);
+ counter(++c_thread_invoke_same);
+
+ thread_unlock(self);
+
+ KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
+ MACHDBG_CODE(DBG_MACH_SCHED,MACH_SCHED) | DBG_FUNC_NONE,
+ self->reason, (uintptr_t)thread_tid(thread), self->sched_pri, thread->sched_pri, 0);
+
+ self->continuation = self->parameter = NULL;
+
+ (void) spllo();
+
+ call_continuation(continuation, parameter, self->wait_result);
+ /*NOTREACHED*/
+ }
+ } else {
+ /*
+ * Check that the other thread has a stack
+ */
+ if (!thread->kernel_stack) {
+need_stack:
+ if (!stack_alloc_try(thread)) {
+ counter(c_thread_invoke_misses++);
+ thread_unlock(thread);
+ thread_stack_enqueue(thread);
+ return (FALSE);
+ }
+ } else if (thread == self) {
+ ast_context(self);
+ counter(++c_thread_invoke_same);
+ thread_unlock(self);
+
+ KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
+ MACHDBG_CODE(DBG_MACH_SCHED,MACH_SCHED) | DBG_FUNC_NONE,
+ self->reason, (uintptr_t)thread_tid(thread), self->sched_pri, thread->sched_pri, 0);
+
+ return (TRUE);
+ }
+ }
+
+ /*
+ * Context switch by full context save.
+ */
+ processor = current_processor();
+ processor->active_thread = thread;
+ processor->current_pri = thread->sched_pri;
+ processor->current_thmode = thread->sched_mode;
+ processor->current_sfi_class = thread->sfi_class;
+ if (thread->last_processor != processor && thread->last_processor != NULL) {
+ if (thread->last_processor->processor_set != processor->processor_set)
+ thread->ps_switch++;
+ thread->p_switch++;
+ }
+ thread->last_processor = processor;
+ thread->c_switch++;
+ ast_context(thread);
+
+ thread_unlock(thread);
+
+ counter(c_thread_invoke_csw++);
+
+ self->reason = reason;
+
+ processor->last_dispatch = ctime;
+ self->last_run_time = ctime;
+ thread_timer_event(ctime, &thread->system_timer);
+ PROCESSOR_DATA(processor, kernel_timer) = &thread->system_timer;
+
+ /*
+ * Since non-precise user/kernel time doesn't update the state timer
+ * during privilege transitions, synthesize an event now.
+ */
+ if (!thread->precise_user_kernel_time) {
+ timer_switch(PROCESSOR_DATA(processor, current_state),
+ ctime,
+ PROCESSOR_DATA(processor, current_state));
+ }
+
+ KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
+ MACHDBG_CODE(DBG_MACH_SCHED,MACH_SCHED) | DBG_FUNC_NONE,
+ self->reason, (uintptr_t)thread_tid(thread), self->sched_pri, thread->sched_pri, 0);
+
+ if ((thread->chosen_processor != processor) && (thread->chosen_processor != NULL)) {
+ SCHED_DEBUG_CHOOSE_PROCESSOR_KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_SCHED, MACH_MOVED)|DBG_FUNC_NONE,
+ (uintptr_t)thread_tid(thread), (uintptr_t)thread->chosen_processor->cpu_id, 0, 0, 0);
+ }
+
+ DTRACE_SCHED2(off__cpu, struct thread *, thread, struct proc *, thread->task->bsd_info);
+
+ SCHED_STATS_CSW(processor, self->reason, self->sched_pri, thread->sched_pri);
+
+ /*
+ * This is where we actually switch register context,
+ * and address space if required. We will next run
+ * as a result of a subsequent context switch.
+ *
+ * Once registers are switched and the processor is running "thread",
+ * the stack variables and non-volatile registers will contain whatever
+ * was there the last time that thread blocked. No local variables should
+ * be used after this point, except for the special case of "thread", which
+ * the platform layer returns as the previous thread running on the processor
+ * via the function call ABI as a return register, and "self", which may have
+ * been stored on the stack or a non-volatile register, but a stale idea of
+ * what was on the CPU is newly-accurate because that thread is again
+ * running on the CPU.
+ */
+ assert(continuation == self->continuation);
+ thread = machine_switch_context(self, continuation, thread);
+ assert(self == current_thread());
+ TLOG(1,"thread_invoke: returning machine_switch_context: self %p continuation %p thread %p\n", self, continuation, thread);
+
+ DTRACE_SCHED(on__cpu);
+
+ /*
+ * We have been resumed and are set to run.
+ */
+ thread_dispatch(thread, self);
+
+ if (continuation) {
+ self->continuation = self->parameter = NULL;
+
+ (void) spllo();
+
+ call_continuation(continuation, parameter, self->wait_result);
+ /*NOTREACHED*/
+ }
+
+ return (TRUE);
+}
+
+#if defined(CONFIG_SCHED_DEFERRED_AST)
+/*
+ * pset_cancel_deferred_dispatch:
+ *
+ * Cancels all ASTs that we can cancel for the given processor set
+ * if the current processor is running the last runnable thread in the
+ * system.
+ *
+ * This function assumes the current thread is runnable. This must
+ * be called with the pset unlocked.
+ */
+static void
+pset_cancel_deferred_dispatch(
+ processor_set_t pset,
+ processor_t processor)
+{
+ processor_t active_processor = NULL;
+ uint32_t sampled_sched_run_count;
+
+ pset_lock(pset);
+ sampled_sched_run_count = (volatile uint32_t) sched_run_count;
+
+ /*
+ * If we have emptied the run queue, and our current thread is runnable, we
+ * should tell any processors that are still DISPATCHING that they will
+ * probably not have any work to do. In the event that there are no
+ * pending signals that we can cancel, this is also uninteresting.
+ *
+ * In the unlikely event that another thread becomes runnable while we are
+ * doing this (sched_run_count is atomically updated, not guarded), the
+ * codepath making it runnable SHOULD (a dangerous word) need the pset lock
+ * in order to dispatch it to a processor in our pset. So, the other
+ * codepath will wait while we squash all cancelable ASTs, get the pset
+ * lock, and then dispatch the freshly runnable thread. So this should be
+ * correct (we won't accidentally have a runnable thread that hasn't been
+ * dispatched to an idle processor), if not ideal (we may be restarting the
+ * dispatch process, which could have some overhead).
+ *
+ */
+ if ((sampled_sched_run_count == 1) &&
+ (pset->pending_deferred_AST_cpu_mask)) {
+ qe_foreach_element_safe(active_processor, &pset->active_queue, processor_queue) {
+ /*
+ * If a processor is DISPATCHING, it could be because of
+ * a cancelable signal.
+ *
+ * IF the processor is not our
+ * current processor (the current processor should not
+ * be DISPATCHING, so this is a bit paranoid), AND there
+ * is a cancelable signal pending on the processor, AND
+ * there is no non-cancelable signal pending (as there is
+ * no point trying to backtrack on bringing the processor
+ * up if a signal we cannot cancel is outstanding), THEN
+ * it should make sense to roll back the processor state
+ * to the IDLE state.
+ *
+ * If the racey nature of this approach (as the signal
+ * will be arbitrated by hardware, and can fire as we
+ * roll back state) results in the core responding
+ * despite being pushed back to the IDLE state, it
+ * should be no different than if the core took some
+ * interrupt while IDLE.
+ */
+ if ((active_processor->state == PROCESSOR_DISPATCHING) &&
+ (pset->pending_deferred_AST_cpu_mask & (1ULL << active_processor->cpu_id)) &&
+ (!(pset->pending_AST_cpu_mask & (1ULL << active_processor->cpu_id))) &&
+ (active_processor != processor)) {
+ /*
+ * Squash all of the processor state back to some
+ * reasonable facsimile of PROCESSOR_IDLE.
+ *
+ * TODO: What queue policy do we actually want here?
+ * We want to promote selection of a good processor
+ * to run on. Do we want to enqueue at the head?
+ * The tail? At the (relative) old position in the
+ * queue? Or something else entirely?
+ */
+ re_queue_head(&pset->idle_queue, (queue_entry_t)active_processor);
+
+ assert(active_processor->next_thread == THREAD_NULL);
+
+ active_processor->current_pri = IDLEPRI;
+ active_processor->current_thmode = TH_MODE_FIXED;
+ active_processor->current_sfi_class = SFI_CLASS_KERNEL;
+ active_processor->deadline = UINT64_MAX;
+ active_processor->state = PROCESSOR_IDLE;
+ pset->pending_deferred_AST_cpu_mask &= ~(1U << active_processor->cpu_id);
+ machine_signal_idle_cancel(active_processor);
+ }
+
+ }
+ }
+
+ pset_unlock(pset);
+}
+#else
+/* We don't support deferred ASTs; everything is candycanes and sunshine. */
+#endif
+
+/*
+ * thread_dispatch:
+ *
+ * Handle threads at context switch. Re-dispatch other thread
+ * if still running, otherwise update run state and perform
+ * special actions. Update quantum for other thread and begin
+ * the quantum for ourselves.
+ *
+ * "thread" is the old thread that we have switched away from.
+ * "self" is the new current thread that we have context switched to
+ *
+ * Called at splsched.
+ */
+void
+thread_dispatch(
+ thread_t thread,
+ thread_t self)
+{
+ processor_t processor = self->last_processor;
+
+ assert(processor == current_processor());
+ assert(self == current_thread());
+ assert(thread != self);
+
+ if (thread != THREAD_NULL) {
+ /*
+ * If blocked at a continuation, discard
+ * the stack.
+ */
+ if (thread->continuation != NULL && thread->kernel_stack != 0)
+ stack_free(thread);
+
+ if (thread->state & TH_IDLE) {
+ KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
+ MACHDBG_CODE(DBG_MACH_SCHED,MACH_DISPATCH) | DBG_FUNC_NONE,
+ (uintptr_t)thread_tid(thread), 0, thread->state, sched_run_count, 0);
+ } else {
+ int64_t consumed;
+ int64_t remainder = 0;
+
+ if (processor->quantum_end > processor->last_dispatch)
+ remainder = processor->quantum_end -
+ processor->last_dispatch;
+
+ consumed = thread->quantum_remaining - remainder;
+
+ if ((thread->reason & AST_LEDGER) == 0) {
+ /*
+ * Bill CPU time to both the task and
+ * the individual thread.
+ */
+ ledger_credit(thread->t_ledger,
+ task_ledgers.cpu_time, consumed);
+ ledger_credit(thread->t_threadledger,
+ thread_ledgers.cpu_time, consumed);
+#ifdef CONFIG_BANK
+ if (thread->t_bankledger) {
+ ledger_credit(thread->t_bankledger,
+ bank_ledgers.cpu_time,
+ (consumed - thread->t_deduct_bank_ledger_time));
+
+ }
+ thread->t_deduct_bank_ledger_time =0;
+#endif
+ }
+
+ wake_lock(thread);
+ thread_lock(thread);
+
+ /*
+ * Compute remainder of current quantum.
+ */
+ if (processor->first_timeslice &&
+ processor->quantum_end > processor->last_dispatch)
+ thread->quantum_remaining = (uint32_t)remainder;
+ else
+ thread->quantum_remaining = 0;
+
+ if (thread->sched_mode == TH_MODE_REALTIME) {
+ /*
+ * Cancel the deadline if the thread has
+ * consumed the entire quantum.
+ */
+ if (thread->quantum_remaining == 0) {
+ thread->realtime.deadline = UINT64_MAX;
+ }
+ } else {
+#if defined(CONFIG_SCHED_TIMESHARE_CORE)
+ /*
+ * For non-realtime threads treat a tiny
+ * remaining quantum as an expired quantum
+ * but include what's left next time.
+ */
+ if (thread->quantum_remaining < min_std_quantum) {
+ thread->reason |= AST_QUANTUM;
+ thread->quantum_remaining += SCHED(initial_quantum_size)(thread);
+ }
+#endif /* CONFIG_SCHED_TIMESHARE_CORE */
+ }
+
+ /*
+ * If we are doing a direct handoff then
+ * take the remainder of the quantum.
+ */
+ if ((thread->reason & (AST_HANDOFF|AST_QUANTUM)) == AST_HANDOFF) {
+ self->quantum_remaining = thread->quantum_remaining;
+ thread->reason |= AST_QUANTUM;
+ thread->quantum_remaining = 0;
+ } else {
+#if defined(CONFIG_SCHED_MULTIQ)
+ if (SCHED(sched_groups_enabled) &&
+ thread->sched_group == self->sched_group) {
+ KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
+ MACHDBG_CODE(DBG_MACH_SCHED, MACH_QUANTUM_HANDOFF),
+ self->reason, (uintptr_t)thread_tid(thread),
+ self->quantum_remaining, thread->quantum_remaining, 0);
+
+ self->quantum_remaining = thread->quantum_remaining;
+ thread->quantum_remaining = 0;
+ /* Don't set AST_QUANTUM here - old thread might still want to preempt someone else */
+ }
+#endif /* defined(CONFIG_SCHED_MULTIQ) */
+ }
+
+ thread->computation_metered += (processor->last_dispatch - thread->computation_epoch);
+
+ if ((thread->rwlock_count != 0) && !(LcksOpts & disLkRWPrio)) {
+ integer_t priority;
+
+ priority = thread->sched_pri;
+
+ if (priority < thread->base_pri)
+ priority = thread->base_pri;
+ if (priority < BASEPRI_BACKGROUND)
+ priority = BASEPRI_BACKGROUND;
+
+ if ((thread->sched_pri < priority) || !(thread->sched_flags & TH_SFLAG_RW_PROMOTED)) {
+ KERNEL_DEBUG_CONSTANT(
+ MACHDBG_CODE(DBG_MACH_SCHED, MACH_RW_PROMOTE) | DBG_FUNC_NONE,
+ (uintptr_t)thread_tid(thread), thread->sched_pri, thread->base_pri, priority, 0);
+
+ thread->sched_flags |= TH_SFLAG_RW_PROMOTED;
+
+ if (thread->sched_pri < priority)
+ set_sched_pri(thread, priority);
+ }
+ }
+
+ if (!(thread->state & TH_WAIT)) {
+ /*
+ * Still runnable.
+ */
+ thread->last_made_runnable_time = mach_approximate_time();
+
+ machine_thread_going_off_core(thread, FALSE);
+
+ if (thread->reason & AST_QUANTUM)
+ thread_setrun(thread, SCHED_TAILQ);
+ else if (thread->reason & AST_PREEMPT)
+ thread_setrun(thread, SCHED_HEADQ);
+ else
+ thread_setrun(thread, SCHED_PREEMPT | SCHED_TAILQ);
+
+ KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
+ MACHDBG_CODE(DBG_MACH_SCHED,MACH_DISPATCH) | DBG_FUNC_NONE,
+ (uintptr_t)thread_tid(thread), thread->reason, thread->state, sched_run_count, 0);
+
+ if (thread->wake_active) {
+ thread->wake_active = FALSE;
+ thread_unlock(thread);
+
+ thread_wakeup(&thread->wake_active);
+ } else {
+ thread_unlock(thread);
+ }
+
+ wake_unlock(thread);
+ } else {
+ /*
+ * Waiting.
+ */
+ boolean_t should_terminate = FALSE;
+ uint32_t new_run_count;
+
+ /* Only the first call to thread_dispatch
+ * after explicit termination should add
+ * the thread to the termination queue
+ */
+ if ((thread->state & (TH_TERMINATE|TH_TERMINATE2)) == TH_TERMINATE) {
+ should_terminate = TRUE;
+ thread->state |= TH_TERMINATE2;
+ }
+
+ thread->state &= ~TH_RUN;
+ thread->last_made_runnable_time = ~0ULL;
+ thread->chosen_processor = PROCESSOR_NULL;
+
+ if (thread->sched_mode == TH_MODE_TIMESHARE) {
+ if (thread->sched_flags & TH_SFLAG_THROTTLED)
+ sched_background_decr(thread);
+
+ sched_share_decr(thread);
+ }
+ new_run_count = sched_run_decr(thread);
+
+#if CONFIG_SCHED_SFI
+ if ((thread->state & (TH_WAIT | TH_TERMINATE)) == TH_WAIT) {
+ if (thread->reason & AST_SFI) {
+ thread->wait_sfi_begin_time = processor->last_dispatch;
+ }
+ }
+#endif
+
+ machine_thread_going_off_core(thread, should_terminate);
+
+ KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
+ MACHDBG_CODE(DBG_MACH_SCHED,MACH_DISPATCH) | DBG_FUNC_NONE,
+ (uintptr_t)thread_tid(thread), thread->reason, thread->state, new_run_count, 0);
+
+ (*thread->sched_call)(SCHED_CALL_BLOCK, thread);
+
+ if (thread->wake_active) {
+ thread->wake_active = FALSE;
+ thread_unlock(thread);
+
+ thread_wakeup(&thread->wake_active);
+ } else {
+ thread_unlock(thread);
+ }
+
+ wake_unlock(thread);
+
+ if (should_terminate)
+ thread_terminate_enqueue(thread);
+ }
+ }
+ }
+
+ /* Update (new) current thread and reprogram quantum timer */
+ thread_lock(self);
+ if (!(self->state & TH_IDLE)) {
+ uint64_t arg1, arg2;
+ int urgency;
+ uint64_t latency;
+
+#if CONFIG_SCHED_SFI
+ ast_t new_ast;
+
+ new_ast = sfi_thread_needs_ast(self, NULL);
+
+ if (new_ast != AST_NONE) {
+ ast_on(new_ast);
+ }
+#endif
+
+ assert(processor->last_dispatch >= self->last_made_runnable_time);
+ latency = processor->last_dispatch - self->last_made_runnable_time;
+
+ urgency = thread_get_urgency(self, &arg1, &arg2);
+
+ thread_tell_urgency(urgency, arg1, arg2, latency, self);
+
+ machine_thread_going_on_core(self, urgency, latency);
+
+ /*
+ * Get a new quantum if none remaining.
+ */
+ if (self->quantum_remaining == 0) {
+ thread_quantum_init(self);
+ }
+
+ /*
+ * Set up quantum timer and timeslice.
+ */
+ processor->quantum_end = processor->last_dispatch + self->quantum_remaining;
+ timer_call_enter1(&processor->quantum_timer, self, processor->quantum_end, TIMER_CALL_SYS_CRITICAL | TIMER_CALL_LOCAL);
+
+ processor->first_timeslice = TRUE;
+ } else {
+ timer_call_cancel(&processor->quantum_timer);
+ processor->first_timeslice = FALSE;
+
+ thread_tell_urgency(THREAD_URGENCY_NONE, 0, 0, 0, self);
+ machine_thread_going_on_core(self, THREAD_URGENCY_NONE, 0);
+ }
+
+ self->computation_epoch = processor->last_dispatch;
+ self->reason = AST_NONE;
+
+ thread_unlock(self);
+
+#if defined(CONFIG_SCHED_DEFERRED_AST)
+ /*
+ * TODO: Can we state that redispatching our old thread is also
+ * uninteresting?
+ */
+ if ((((volatile uint32_t)sched_run_count) == 1) &&
+ !(self->state & TH_IDLE)) {
+ pset_cancel_deferred_dispatch(processor->processor_set, processor);
+ }
+#endif
+
+}
+
+/*
+ * thread_block_reason:
+ *
+ * Forces a reschedule, blocking the caller if a wait
+ * has been asserted.
+ *
+ * If a continuation is specified, then thread_invoke will
+ * attempt to discard the thread's kernel stack. When the
+ * thread resumes, it will execute the continuation function
+ * on a new kernel stack.
+ */
+counter(mach_counter_t c_thread_block_calls = 0;)
+
+wait_result_t
+thread_block_reason(
+ thread_continue_t continuation,
+ void *parameter,
+ ast_t reason)
+{
+ thread_t self = current_thread();
+ processor_t processor;
+ thread_t new_thread;
+ spl_t s;
+
+ counter(++c_thread_block_calls);
+
+ s = splsched();
+
+ processor = current_processor();
+
+ /* If we're explicitly yielding, force a subsequent quantum */
+ if (reason & AST_YIELD)
+ processor->first_timeslice = FALSE;
+
+ /* We're handling all scheduling AST's */
+ ast_off(AST_SCHEDULING);
+
+ self->continuation = continuation;
+ self->parameter = parameter;
+
+ if (self->state & ~(TH_RUN | TH_IDLE)) {
+ KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
+ MACHDBG_CODE(DBG_MACH_SCHED,MACH_BLOCK),
+ reason, VM_KERNEL_UNSLIDE(continuation), 0, 0, 0);
+ }
+
+ do {
+ thread_lock(self);
+ new_thread = thread_select(self, processor, reason);
+ thread_unlock(self);
+ } while (!thread_invoke(self, new_thread, reason));
+
+ splx(s);
+
+ return (self->wait_result);
+}
+
+/*
+ * thread_block:
+ *
+ * Block the current thread if a wait has been asserted.
+ */
+wait_result_t
+thread_block(
+ thread_continue_t continuation)
+{
+ return thread_block_reason(continuation, NULL, AST_NONE);
+}
+
+wait_result_t
+thread_block_parameter(
+ thread_continue_t continuation,
+ void *parameter)
+{
+ return thread_block_reason(continuation, parameter, AST_NONE);
+}
+
+/*
+ * thread_run:
+ *
+ * Switch directly from the current thread to the
+ * new thread, handing off our quantum if appropriate.
+ *
+ * New thread must be runnable, and not on a run queue.
+ *
+ * Called at splsched.
+ */
+int
+thread_run(
+ thread_t self,
+ thread_continue_t continuation,
+ void *parameter,
+ thread_t new_thread)
+{
+ ast_t handoff = AST_HANDOFF;
+
+ self->continuation = continuation;
+ self->parameter = parameter;
+
+ while (!thread_invoke(self, new_thread, handoff)) {
+ processor_t processor = current_processor();
+
+ thread_lock(self);
+ new_thread = thread_select(self, processor, AST_NONE);
+ thread_unlock(self);
+ handoff = AST_NONE;
+ }
+
+ return (self->wait_result);
+}
+
+/*
+ * thread_continue:
+ *
+ * Called at splsched when a thread first receives
+ * a new stack after a continuation.
+ */
+void
+thread_continue(
+ thread_t thread)
+{
+ thread_t self = current_thread();
+ thread_continue_t continuation;
+ void *parameter;
+
+ DTRACE_SCHED(on__cpu);
+
+ continuation = self->continuation;
+ parameter = self->parameter;
+
+ thread_dispatch(thread, self);
+
+ self->continuation = self->parameter = NULL;
+
+ if (thread != THREAD_NULL)
+ (void)spllo();
+
+ TLOG(1, "thread_continue: calling call_continuation \n");
+ call_continuation(continuation, parameter, self->wait_result);
+ /*NOTREACHED*/
+}
+
+void
+thread_quantum_init(thread_t thread)
+{
+ if (thread->sched_mode == TH_MODE_REALTIME) {
+ thread->quantum_remaining = thread->realtime.computation;
+ } else {
+ thread->quantum_remaining = SCHED(initial_quantum_size)(thread);
+ }
+}
+
+uint32_t
+sched_timeshare_initial_quantum_size(thread_t thread)
+{
+ if ((thread == THREAD_NULL) || !(thread->sched_flags & TH_SFLAG_THROTTLED))
+ return std_quantum;
+ else
+ return bg_quantum;
+}
+
+/*
+ * run_queue_init:
+ *
+ * Initialize a run queue before first use.
+ */
+void
+run_queue_init(
+ run_queue_t rq)
+{
+ int i;
+
+ rq->highq = IDLEPRI;
+ for (i = 0; i < NRQBM; i++)
+ rq->bitmap[i] = 0;
+ setbit(MAXPRI - IDLEPRI, rq->bitmap);
+ rq->urgency = rq->count = 0;
+ for (i = 0; i < NRQS; i++)
+ queue_init(&rq->queues[i]);
+}
+
+/*
+ * run_queue_dequeue:
+ *
+ * Perform a dequeue operation on a run queue,
+ * and return the resulting thread.
+ *
+ * The run queue must be locked (see thread_run_queue_remove()
+ * for more info), and not empty.
+ */
+thread_t
+run_queue_dequeue(
+ run_queue_t rq,
+ integer_t options)
+{
+ thread_t thread;
+ queue_t queue = rq->queues + rq->highq;
+
+ if (options & SCHED_HEADQ) {
+ thread = (thread_t)dequeue_head(queue);
+ }
+ else {
+ thread = (thread_t)dequeue_tail(queue);
+ }
+
+ thread->runq = PROCESSOR_NULL;
+ SCHED_STATS_RUNQ_CHANGE(&rq->runq_stats, rq->count);
+ rq->count--;
+ if (SCHED(priority_is_urgent)(rq->highq)) {
+ rq->urgency--; assert(rq->urgency >= 0);
+ }
+ if (queue_empty(queue)) {
+ if (rq->highq != IDLEPRI)
+ clrbit(MAXPRI - rq->highq, rq->bitmap);
+ rq->highq = MAXPRI - ffsbit(rq->bitmap);
+ }