Interrupt mask in the Pendable Service interrupt handler

Hi everyone,

I am currently learning FreeRTOS on ARM Cortex-M. I have a question about the interrupt mask in the Pendable Service interrupt handler.

I understand that the Pendable Service interrupt handler is used by the RTOS to perform a context switch. To protect the critical section used for the context switch, the code in xPortPendSVHandler() function disables interrupts whose priority levels are below “configMAX_SYSCALL_INTERRUPT_PRIORITY”. The code of Pendable Service interrupt handler for Cortex-M3/M4/M7 is shown below:

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. */
        "       str r0, [r2]                    \n" /* Save the new top of stack into the first member of the TCB. */
        "                                       \n"
        "       stmdb sp!, {r0, 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!, {r0, 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"
        "                                       \n"
        "       bx r14                          \n"
        "                                       \n"
        "       .align 4                        \n"
        "pxCurrentTCBConst: .word pxCurrentTCB  \n"
        ::"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY)
    );
}

As shown, before the vTaskSwitchContext() function called, the BASEPRI is set to mask the interrupt priority to disable interrupts whose priority levels are below the “configMAX_SYSCALL_INTERRUPT_PRIORITY”. Then, the BASEPRI is cleared to enable interrupts when the switch context task is finished. The lines that perform this job are:

        "       mov r0, %0                      \n"
        "       msr basepri, r0                 \n"
        "       dsb                             \n"
        "       isb                             \n"
        "       bl vTaskSwitchContext           \n"
        "       mov r0, #0                      \n"
        "       msr basepri, r0                 \n"

My questions are:

(1) Why the BSEPRI is not set to disable interrupts at the beginning of the xPortPendSVHandler() function, and why the BASEPRI is not cleared to enable interrupts at the end of the xPortPendSVHandler() function?

(2) Does it mean that the code sections before setting BASEPRI and after clearing BASEPRI don’t need to be protected? Why?

(3) What do the code sections before setting BASEPRI and after clearing BASEPRI do?

Thanks.

Liu

We always try to keep the critical sections as short as possible - in this case interrupts are designed to nest quite happily so there is no need to protect anything other than the kernel’s own data structures while they are in an inconsistent state (which is inside vTaskSwitchContext()).

I suppose for the same reason the non interrupt code can keep interrupts enabled most of the time - the processor and the code is designed to allow interrupts to interrupt task (non-interrupt code) and to interrupt other interrupts in the same way. The ‘volatile’ registers (those the compiler can mess with) are saved and restored on interrupt entry and exit - preserving state. The interrupt service routines use critical sections in the same way the task code does.

Well here I could just point you to the hardware manual ;o) …they are pushing (saving) then popping (restoring) the registers that get clobbered by the assembly code across the function call.

Hi @rtel,

I’ve a question regarding this section of code.

Why basepri is restored to ‘0’ and not to previous basepri value? Wouldn’t be better the following (pseudo code)?

mask = ulSetInterruptMask ()
vTaskSwitchContext
vClearInterruptMask (mask)

Let’s say that for example (I don’t know if it’s a good idea to keep pendsv/systick to highest priority, but at least it should be ‘legal’):

  • configMAX_SYSCALL_INTERRUPT_PRIORITY = 2
  • portNVIC_PENDSV_PRI = 1
  • portNVIC_SYSTICK_PRI = 1

A task is running and calls vPortEnterCritical: basepri is set to configMAX_SYSCALL_INTERRUPT_PRIORITY. Then systick triggers (since priority is ‘1’ IRQ will execute)

  • in original asm code, exiting from pendsv handler will clear basepri to ‘0’ (but running task would expect basepri to be configMAX_SYSCALL_INTERRUPT_PRIORITY);
  • in modified pseudo code, exiting from pendsv handler will leave basepri to configMAX_SYSCALL_INTERRUPT_PRIORITY;

It is not legal to run PendSV at any other priority than the lowest priority because it returns to the thread mode. This PR is actually removing the ability to configure the priority of PendSV handler - https://github.com/FreeRTOS/FreeRTOS-Kernel/pull/602

1 Like