March 13, 2018

Scheduler Comparison Between Chrome OS EC Firmware and FreeRTOS

Filed under: Firmware — Tags: , , , , — mosioX @ 1:24 pm

In the past I had a chance to work on both Chrome EC firmware and FreeRTOS projects. Both project have strong and week sides to consider. I’ll go into details for their scheduling policies.
Let’s try to explain Chrome EC firmware scheduler first. This firmware has platform depended scheduler layer for every port of different CPU architectures.
Incidentally Chrome EC firmware uses SVC (software exception) to manipulate scheduling policy. This is handled with PendSV in FreeRTOS. As it can be seen the code segment below SVC handler is responsible to peak next task.

/* ec/core/cortex-m/task.c */
void svc_handler(int desched, task_id_t resched)
{
	task_ *current, *next;
#ifdef CONFIG_TASK_PROFILING
	int exc = get_interrupt_context();
	uint64_t t;
#endif

	/*
	 * Push the priority to -1 until the return, to avoid being
	 * interrupted.
	 */
	asm volatile("cpsid f\n"
		     "isb\n");

#ifdef CONFIG_TASK_PROFILING
	/*
	 * SVCall isn't triggered via DECLARE_IRQ(), so it needs to track its
	 * start time explicitly.
	 */
	if (exc == 0xb) {
		exc_start_time = get_time().val;
		svc_calls++;
	}
#endif

	current = current_task;

#ifdef CONFIG_DEBUG_STACK_OVERFLOW
	if (*current->stack != STACK_UNUSED_VALUE) {
		panic_printf("\n\nStack overflow in %s task!\n",
			     task_names[current - tasks]);
#ifdef CONFIG_SOFTWARE_PANIC
		software_panic(PANIC_SW_STACK_OVERFLOW, current - tasks);
#endif
	}
#endif

	if (desched && !current->events) {
		/*
		 * Remove our own ready bit (current - tasks is same as
		 * task_get_current())
		 */
		tasks_ready &= ~(1 << (current - tasks));
	}
	ASSERT(resched <= TASK_ID_COUNT);
	tasks_ready |= 1 << resched;

	ASSERT(tasks_ready & tasks_enabled);
	next = __task_id_to_ptr(__fls(tasks_ready & tasks_enabled));

#ifdef CONFIG_TASK_PROFILING
	/* Track time in interrupts */
	t = get_time().val;
	exc_total_time += (t - exc_start_time);

	/*
	 * Bill the current task for time between the end of the last interrupt
	 * and the start of this one.
	 */
	current->runtime += (exc_start_time - exc_end_time);
	exc_end_time = t;
#else
	/*
	 * Don't chain here from interrupts until the next time an interrupt
	 * sets an event.
	 */
	need_resched_or_profiling = 0;
#endif

	/* Nothing to do */
	if (next == current)
		return;

	/* Switch to new task */
#ifdef CONFIG_TASK_PROFILING
	task_switches++;
#endif
	current_task = next;
	__switchto(current, next);
}

As we can see from the code line 3 to 70 scheduler determines next task for context switch. On the line 73 (right before context switch), algorithm check if context switch is needed.

Now let’s take a look at FreeRTOS approach. FreeRTOS uses different scheme to schedule for the next task.

/* FreeRTOSv9.0.0/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c */
void xPortPendSVHandler( void )
{
	/* This is a naked function. */

	__asm volatile
	(
	"	mrs r0, psp							\n"
	"	isb									\n"
	"										\n"
	"	ldr	r3, pxCurrentTCBConst			\n" /* Get the location of the current TCB. */
	"	ldr	r2, [r3]						\n"
	"										\n"
	"	tst r14, #0x10						\n" /* Is the task using the FPU context?  If so, push high vfp registers. */
	"	it eq								\n"
	"	vstmdbeq r0!, {s16-s31}				\n"
	"										\n"
	"	stmdb r0!, {r4-r11, r14}			\n" /* Save the core registers. */
	"										\n"
	"	str r0, [r2]						\n" /* Save the new top of stack into the first member of the TCB. */
	"										\n"
	"	stmdb sp!, {r3}						\n"
	"	mov r0, %0 							\n"
	"	msr basepri, r0						\n"
	"	dsb									\n"
	"	isb									\n"
	"	bl vTaskSwitchContext				\n"
	"	mov r0, #0							\n"
	"	msr basepri, r0						\n"
	"	ldmia sp!, {r3}						\n"
	"										\n"
	"	ldr r1, [r3]						\n" /* The first item in pxCurrentTCB is the task top of stack. */
	"	ldr r0, [r1]						\n"
	"										\n"
	"	ldmia r0!, {r4-r11, r14}			\n" /* Pop the core registers. */
	"										\n"
	"	tst r14, #0x10						\n" /* Is the task using the FPU context?  If so, pop the high vfp registers too. */
	"	it eq								\n"
	"	vldmiaeq r0!, {s16-s31}				\n"
	"										\n"
	"	msr psp, r0							\n"
	"	isb									\n"
	"										\n"
	#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata workaround. */
		#if WORKAROUND_PMU_CM001 == 1
	"			push { r14 }				\n"
	"			pop { pc }					\n"
		#endif
	#endif
	"										\n"
	"	bx r14								\n"
	"										\n"
	"	.align 4							\n"
	"pxCurrentTCBConst: .word pxCurrentTCB	\n"
	::"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY)
	);
}

When we observe lines between 8 and 26, PendSV handler save the context. On the line 27 scheduler algorithm (bl vTaskSwitchContext) gets called.
As we came to line between 28 to 42 PendSV handler restores the next task. At this point FreeRTOS differs from the task scheduler of Google EC firmware. FreeRTOS always apply the policy save, schedule and restore as contrast to Google EC firmware schedule and switch if needed.

As can be seen in the figure Google EC firmware first check if we need to call context switch function __switchto.

At this point a question can be popped-up in our mind. Why FreeRTOS apply this scheme. I think this is related the historical background of FreeRTOS. I am aware of FreeRTOS at least 10 years and its architecture has been designed to comply with single stack 8-bit micro-controllers. Incremental enhancements, through the history, accumulate this inefficiency for new architectures. On the other hand, Google EC firmware is relative new project and intended to be used for ARM cortex processors.

 

I hope this article would give some insights of scheduling paradigm differences for different firmware projects.

Older Posts »

Powered by WordPress