Author Topic: Nested Interrupts in Cortex Projects  (Read 2470 times)

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Nested Interrupts in Cortex Projects
« on: August 18, 2019, 03:32:54 AM »
Hi All

Cortex processors support nested interrupts, which means that interrupts with a higher priority than the present interrupt can interrupt the present interrupt.
This allows prioritising interrupt handling but at the same time means that each interrupt routine has to be careful with its use of variables and subroutines if it knows that it could be interrupted by others that are accessing the same variables or could both use subroutines that are not designed to be thread-safe.

uTasker driver interrupt handlers use the following strategy by default: they disable the global interrupt when user interrupt callbacks are executed. This can be seen in the looking at the interrupt handler in the Kinetis Periodic Interrupt Timer:

        if ((ptrCtl->PIT_TCTRL & (PIT_TCTRL_TIE | PIT_TCTRL_TEN)) == (PIT_TCTRL_TIE | PIT_TCTRL_TEN)) { // if the channel and its interrupt are enabled
            if ((ptrCtl->PIT_TFLG & PIT_TFLG_TIF) != 0) {                // {8} if this channel's interrupt has fired
                WRITE_ONE_TO_CLEAR(ptrCtl->PIT_TFLG, PIT_TFLG_TIF);      // clear pending interrupts
                if ((ucPITmodes & (PIT_PERIODIC << (iChannel * 2))) == 0) { // if not periodic mode (single-shot usage)
                    fnDisablePIT(iChannel);                              // stop PIT operation and power down when no other activity
                }
                uDisable_Interrupt();
                    pit_interrupt_handler[iChannel]();                   // call handling function
                uEnable_Interrupt();

                if (IS_POWERED_UP(6, PIT0) == 0) {                       // if the PIT module has been powered down we return rather than checking further channels
                    return;
                }
            }
        }


Assuming the PIT interrupt has a priority of 2 it can be interrupted (nested interrupt) by higher priority interrupts during its execution up to the point where uDisable_Interrupt() is called. When the user interrupt callback is executed [pit_interrupt_handler[iChannel]() in the example] all interrupts are disabled and so the user doesn't need to 'worry' about being interrupted by a higher priority interrupt. There is therefore no 'risk' the variables that it is accessing could be modified by other interrupts with higher priority or that subroutine calls that it uses could fail since they haven't been designed to allow being called but other interrupts that could take place during their execution.
Although there may be a potential disadvantage in not allowing higher priority interrupts to be executed this technique ensures reliability (by default) without needing to consider carefully any dangers that nested interrupts could pose.


If the user does prefer to allow higher priority interrupts to be taken during the execution of the handler the user can add
uEnable_Interrupt();
...
uDisable_Interrupt();

around the code in the interrupt callback that should allow nested interrupt operation, in which case higher priority interrupts can be taken.
The user must however carefully consider what could happen when other interrupts interrupt the code - could the other interrupts potentially cause variable corruption when both could modify the same variable? And are used subroutines safe to be called by the present interrupt and potentially be interrupted and called by a higher priority one?


In the case of Cortex M3/M4/M7 (but not Cortex M0, which doesn't have the capability) a further strategy is supported in the uTasker project [check the exact project version used to see whether this support is included] and requires the following defines to be enabled and set accordingly


    #define SYSTEM_NO_DISABLE_LEVEL       1                              // allow interrupts of higher priority than this to not be blocked in critical regions
    #define LOWEST_PRIORITY_PREEMPT_LEVEL 0                              // normal level is for all interrupts to be able to operate


When SYSTEM_NO_DISABLE_LEVEL is enabled the effect of
uEnable_Interrupt()
and
uDisable_Interrupt()
changes from disabling and enabling the global interrupt to changing the interrupt acceptance mask (using the BASEPRI "Base Priority Mask Register" as detailed at http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0646a/CHDBIBGJ.html).

When the user callback is executed, all interrupt below a certain priority level (greater priority than 1 in the example) are still allowed to be nested but not interrupts of equal or lower priority.
This allows the user to easily specify which interrupts (higher than SYSTEM_NO_DISABLE_LEVEL) are allowed to interrupt lower priority ones and which ones are not (lower or equal to SYSTEM_NO_DISABLE_LEVEL).

Again it is up to the user to carefully check that interrupts that can nest others are not potentially corrupting shared variables or using subroutines that are not safe to use when a lower priority interrupt is already executing them.

Regards

Mark