+ /*
+ * 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);
+
+#if KPERF
+ kperf_on_cpu(self, NULL, __builtin_frame_address(0));
+#endif /* KPERF */
+
+ /*
+ * 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_buckets[TH_BUCKET_RUN];
+
+ /*
+ * 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, &active_processor->processor_queue);
+
+ 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_buckets[TH_BUCKET_RUN], 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);
+
+ /*
+ * Apply a priority floor if the thread holds a kernel resource
+ * Do this before checking starting_pri to avoid overpenalizing
+ * repeated rwlock blockers.
+ */
+ if (__improbable(thread->rwlock_count != 0))
+ lck_rw_set_promotion_locked(thread);
+
+ boolean_t keep_quantum = processor->first_timeslice;
+
+ /*
+ * Treat a thread which has dropped priority since it got on core
+ * as having expired its quantum.
+ */
+ if (processor->starting_pri > thread->sched_pri)
+ keep_quantum = FALSE;
+
+ /* Compute remainder of current quantum. */
+ if (keep_quantum &&
+ 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->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_buckets[TH_BUCKET_RUN], 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;
+
+ 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
+
+ assertf(processor->last_dispatch >= self->last_made_runnable_time, "Non-monotonic time? dispatch at 0x%llx, runnable at 0x%llx", 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;
+ processor->starting_pri = self->sched_pri;
+
+ 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_buckets[TH_BUCKET_RUN]) == 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);
+
+#if PROC_REF_DEBUG
+ if ((continuation != NULL) && (self->task != kernel_task)) {
+ if (uthread_get_proc_refcount(self->uthread) != 0) {
+ panic("thread_block_reason with continuation uthread %p with uu_proc_refcount != 0", self->uthread);
+ }
+ }
+#endif
+
+ 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;
+
+#if KPERF
+ kperf_on_cpu(self, continuation, NULL);
+#endif
+
+ 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->th_sched_bucket == TH_BUCKET_SHARE_BG)
+ return bg_quantum;
+ else
+ return std_quantum;
+}
+
+/*
+ * run_queue_init:
+ *
+ * Initialize a run queue before first use.
+ */
+void
+run_queue_init(
+ run_queue_t rq)
+{
+ rq->highq = NOPRI;
+ for (u_int i = 0; i < BITMAP_LEN(NRQS); i++)
+ rq->bitmap[i] = 0;
+ rq->urgency = rq->count = 0;
+ for (int i = 0; i < NRQS; i++)
+ queue_init(&rq->queues[i]);
+}