Index: kernel/generic/include/proc/scheduler.h
===================================================================
--- kernel/generic/include/proc/scheduler.h	(revision 8996582a5a68dd2f404f0d237aa5a427d746962c)
+++ kernel/generic/include/proc/scheduler.h	(revision 151c0503e1c1b615f0f4fe97cc31a4dfe16b78df)
@@ -41,4 +41,5 @@
 #include <atomic.h>
 #include <adt/list.h>
+#include <abi/proc/thread.h>
 
 #define RQ_COUNT          16
@@ -56,9 +57,10 @@
 
 extern void scheduler_fpu_lazy_request(void);
-extern void scheduler(void);
-extern void scheduler_locked(ipl_t);
 extern void kcpulb(void *arg);
 
 extern void sched_print_list(void);
+
+extern void scheduler_run(void) __attribute__((noreturn));
+extern void scheduler_enter(state_t);
 
 /*
Index: kernel/generic/include/proc/thread.h
===================================================================
--- kernel/generic/include/proc/thread.h	(revision 8996582a5a68dd2f404f0d237aa5a427d746962c)
+++ kernel/generic/include/proc/thread.h	(revision 151c0503e1c1b615f0f4fe97cc31a4dfe16b78df)
@@ -113,5 +113,4 @@
 	 */
 	context_t saved_context;
-	ipl_t saved_ipl;
 
 	/**
@@ -244,4 +243,6 @@
 extern errno_t thread_join_timeout(thread_t *, uint32_t, unsigned int);
 
+extern void thread_yield(void);
+
 extern void thread_print_list(bool);
 extern thread_t *thread_find_by_id(thread_id_t);
Index: kernel/generic/src/main/main.c
===================================================================
--- kernel/generic/src/main/main.c	(revision 8996582a5a68dd2f404f0d237aa5a427d746962c)
+++ kernel/generic/src/main/main.c	(revision 151c0503e1c1b615f0f4fe97cc31a4dfe16b78df)
@@ -285,8 +285,8 @@
 
 	/*
-	 * This call to scheduler() will return to kinit,
+	 * This call to scheduler_run() will return to kinit,
 	 * starting the thread of kernel threads.
 	 */
-	scheduler();
+	scheduler_run();
 	/* not reached */
 }
@@ -356,5 +356,5 @@
 
 	semaphore_up(&ap_completion_semaphore);
-	scheduler();
+	scheduler_run();
 	/* not reached */
 }
Index: kernel/generic/src/proc/scheduler.c
===================================================================
--- kernel/generic/src/proc/scheduler.c	(revision 8996582a5a68dd2f404f0d237aa5a427d746962c)
+++ kernel/generic/src/proc/scheduler.c	(revision 151c0503e1c1b615f0f4fe97cc31a4dfe16b78df)
@@ -302,16 +302,19 @@
 }
 
-void scheduler(void)
-{
-	ipl_t ipl = interrupts_disable();
-
-	if (atomic_load(&haltstate))
-		halt();
-
-	if (THREAD) {
-		irq_spinlock_lock(&THREAD->lock, false);
-	}
-
-	scheduler_locked(ipl);
+void scheduler_run(void)
+{
+	assert(interrupts_disabled());
+	assert(THREAD == NULL);
+	assert(CPU != NULL);
+
+	current_copy(CURRENT, (current_t *) CPU_LOCAL->stack);
+
+	context_t ctx;
+	context_save(&ctx);
+	context_set(&ctx, FADDR(scheduler_separated_stack),
+	    (uintptr_t) CPU_LOCAL->stack, STACK_SIZE);
+	context_restore(&ctx);
+
+	unreachable();
 }
 
@@ -431,32 +434,27 @@
  *
  */
-void scheduler_locked(ipl_t ipl)
-{
+void scheduler_enter(state_t new_state)
+{
+	ipl_t ipl = interrupts_disable();
+
 	assert(CPU != NULL);
-
-	if (THREAD) {
-		/* Update thread kernel accounting */
-		THREAD->kcycles += get_cycle() - THREAD->last_cycle;
-
-		fpu_cleanup();
-
-		if (!context_save(&THREAD->saved_context)) {
-			/*
-			 * This is the place where threads leave scheduler();
-			 */
-
-			irq_spinlock_unlock(&THREAD->lock, false);
-			interrupts_restore(THREAD->saved_ipl);
-
-			return;
-		}
-
-		/*
-		 * Interrupt priority level of preempted thread is recorded
-		 * here to facilitate scheduler() invocations from
-		 * interrupts_disable()'d code (e.g. waitq_sleep_timeout()).
-		 *
-		 */
-		THREAD->saved_ipl = ipl;
+	assert(THREAD != NULL);
+
+	fpu_cleanup();
+
+	irq_spinlock_lock(&THREAD->lock, false);
+	THREAD->state = new_state;
+
+	/* Update thread kernel accounting */
+	THREAD->kcycles += get_cycle() - THREAD->last_cycle;
+
+	if (!context_save(&THREAD->saved_context)) {
+		/*
+		 * This is the place where threads leave scheduler();
+		 */
+
+		irq_spinlock_unlock(&THREAD->lock, false);
+		interrupts_restore(ipl);
+		return;
 	}
 
@@ -504,4 +502,7 @@
 	assert(interrupts_disabled());
 
+	if (atomic_load(&haltstate))
+		halt();
+
 	if (THREAD) {
 		after_thread_ran_arch();
@@ -678,5 +679,5 @@
 		 *
 		 */
-		scheduler();
+		thread_yield();
 	} else {
 		/*
Index: kernel/generic/src/proc/thread.c
===================================================================
--- kernel/generic/src/proc/thread.c	(revision 8996582a5a68dd2f404f0d237aa5a427d746962c)
+++ kernel/generic/src/proc/thread.c	(revision 151c0503e1c1b615f0f4fe97cc31a4dfe16b78df)
@@ -314,8 +314,4 @@
 	current_initialize((current_t *) thread->kstack);
 
-	ipl_t ipl = interrupts_disable();
-	thread->saved_ipl = interrupts_read();
-	interrupts_restore(ipl);
-
 	str_cpy(thread->name, THREAD_NAME_BUFLEN, name);
 
@@ -518,11 +514,6 @@
 	}
 
-	irq_spinlock_lock(&THREAD->lock, true);
-	THREAD->state = Exiting;
-	irq_spinlock_unlock(&THREAD->lock, true);
-
-	scheduler();
-
-	panic("should never be reached");
+	scheduler_enter(Exiting);
+	unreachable();
 }
 
@@ -623,8 +614,5 @@
 	}
 
-	ipl_t ipl = interrupts_disable();
-	irq_spinlock_lock(&THREAD->lock, false);
-	THREAD->state = Sleeping;
-	scheduler_locked(ipl);
+	scheduler_enter(Sleeping);
 
 	if (deadline != DEADLINE_NEVER && !timeout_unregister(&timeout)) {
@@ -736,4 +724,11 @@
 
 	(void) waitq_sleep_timeout(&wq, usec);
+}
+
+/** Allow other threads to run. */
+void thread_yield(void)
+{
+	assert(THREAD != NULL);
+	scheduler_enter(Running);
 }
 
Index: kernel/generic/src/time/clock.c
===================================================================
--- kernel/generic/src/time/clock.c	(revision 8996582a5a68dd2f404f0d237aa5a427d746962c)
+++ kernel/generic/src/time/clock.c	(revision 151c0503e1c1b615f0f4fe97cc31a4dfe16b78df)
@@ -187,5 +187,5 @@
 	if (THREAD) {
 		if (current_clock_tick >= CPU_LOCAL->preempt_deadline && PREEMPTION_ENABLED) {
-			scheduler();
+			thread_yield();
 #ifdef CONFIG_UDEBUG
 			/*
