Moving away from the single core STM32F4 brought me to the RP2040 - a dual core M0+ created by Raspberry Pi Foundation. I find the documentation for the chip itself quite good and the actual softwer support quite extensive covering the entire range of functionality of the chip. The actual target for it is the hobby market (as expected) with focuss on embeded Python. I'm more interested actually on the low level side of things so I prefer digging into the the provided SW support and creating my own fork of the SDK with a few tweaks.
One of the extra features that I wanted to explore is the SMP port of the FreeRTOS OS. It really interested me how this is done in this case as in comparison with the ESP32.
One of the major concepts for SMP (Symmetric Multi Processing) is the ability of the kernel to perform context switches on both cores and allow a task to be scheduled on either core. This is realized by having the scheduler started on both cores and performing their own context switching. A single SysTick timer is used, the second core periodic yield will be triggered by using the RP2040 FIFO.
Just disabling interups is no more a guarantee that the resources needed by FreeRTOS are locked. Because of this the 2 of the spinlocks provided by the pico are used. By convenience they are already reserved by the creators of the pico sdk.
The actual start point for the OS is the stack organization. I did a few changes in comparison with the official port (basically coming from the M4 where there are less restrictions on the programmer's model). The first is the way that the first task is started: I wanted to get the first task started by the SVC Handler by droping from Handler mode to Thread mode. This means that the PC address needs to be masked with 0XFFFFFFFE (last bit 0).
To actually start a first task the msp is set back first to the beginning of the stack (to make use of all available bytes) and the svc is called with parameter 0. From this point on interrupts are active and working. To select the corect stack pointer the core index is passed as a parameter in r0.
From the SVC Handler the first task context can be actually prepared:
- Discard the r11-r4 register values from the stack as they are not needed now (initial value)
- set the process stack pointer
- return from handler mode by branching to 0XFFFFFFFD (EXC_RETURN code)
The return from handler mode to thread mode has the desired effect of changing the used stack pointer from msp to psp and will pop from the process stack the registetrs r0-r3, r12, lr, pc and xPSR. Here we also make use of the core number in order to index the pointer to the TCBs.
After the first task is started the normal context switching will apply being triggerd by the yield call (PendSV handler) and performing the context sitch using the current TCB pointer indexed off course by the core number. Depending on what vTaskSwitchContext places in the pointer tasks that have runned previously on the first core can also be scheduled here. There is quite some extensive documentation on the switching strategy that can be read in the official FreeRTOS repo.
Source code for my examples: pico examples
FreeRTOS SMP Documentation: FreeRTOS SMP Documentaion.pdf