Exception (Interrupt) Masking Mechanism of ARM Cortex-M3/M4

The interrupt system of Cortex-M3 is central to its implementation of real-time performance and efficient task scheduling, integrating advanced features of the ARMv7-M architecture. In certain application scenarios (such as critical sections), interrupt masking operations are often required to prevent the program from being interrupted during specific operations, similar to atomic operations. Therefore, interrupt masking is an inevitable part of common programs, and the relevant registers for implementing interrupt masking are PRIMASK, FAULTMASK, and BASEPRI.

The PRIMASK, FAULTMASK, and BASEPRI registers are all used for exception or interrupt masking, with each exception (including interrupts) having a priority level, where a lower numerical value indicates a higher priority, and a higher numerical value indicates a lower priority. These special registers can mask exceptions based on priority levels, and they can only be manipulated at privileged access levels (write operations in non-privileged states are ignored, while reads return 0). They are all initialized to 0, meaning that masking (disabling exceptions/interrupts) is ineffective. All these registers are 32 bits wide, but only a portion of them is actually used, as shown in the programming model diagram:

Exception (Interrupt) Masking Mechanism of ARM Cortex-M3/M4

PRIMASK

The PRIMASK register is a 1-bit wide interrupt masking register, utilizing only the 0th bit. When set, it prevents all exceptions (including interrupts) except for non-maskable interrupts (NMI) and HardFault exceptions. In effect, it raises the current exception priority to 0, which is the highest priority for programmable exceptions/interrupts.

The most common use of PRIMASK is to disable all interrupts in time-critical processes, and after the process is completed, PRIMASK needs to be cleared to re-enable interrupts.

Assembly language can access the PRIMASK register using the MRS and MSR instructions, and interrupt masking control can also be implemented using CPSIE I and CPSID I. In CMSIS-Core, C language functions are provided to mask all interrupts, with the internal implementation being inline assembly, as shown below:

/* The following functions are provided by CMSIS and can be directly called during application development */
// Enable interrupts (clear PRIMASK)__STATIC_FORCEINLINE void __enable_irq(void){    __ASM volatile ("cpsie i" : : : "memory");}
// Disable interrupts (set PRIMASK)__STATIC_FORCEINLINE void __disable_irq(void){    __ASM volatile ("cpsid i" : : : "memory");}
// Directly manipulating the value of PRIMASK can also enable or disable interrupts__STATIC_FORCEINLINE void __set_PRIMASK(uint32_t priMask){    __ASM volatile ("MSR primask, %0" : : "r" (priMask) : "memory");}
// Get the value of PRIMASK to check if interrupts are masked__STATIC_FORCEINLINE uint32_t __get_PRIMASK(void){    uint32_t result;
    __ASM volatile ("MRS %0, primask" : "=r" (result) );    return (result);}

FAULTMASK

The FAULTMASK is very similar to PRIMASK, but it can also mask HardFault exceptions, effectively raising the exception priority to -1. Error handling code can use FAULTMASK to prevent other errors from being triggered during error handling (only a few types). For example, FAULTMASK can be used to bypass the MPU or mask bus errors (which are configurable), making it easier for error handling code to execute corrective measures. Unlike PRIMASK, FAULTMASK is automatically cleared upon returning from an exception.

In terms of usage, FAULTMASK is used to elevate the priority of configured error handling (such as MemManage, BusFault, and UsageFault) to -1, allowing these errors to utilize some special features of HardFault.

FAULTMASK can only be accessed in privileged mode, but it cannot be set during NMI and HardFault handling. Assembly language can access the PRIMASK register using MRS and MSR instructions, and error interrupt masking control can be implemented using CPSIE F and CPSID F. CMSIS-Core provides C language functions, which are also based on inline assembly:

/* The following functions are provided by CMSIS and can be directly called during application development */
// Enable fault interrupts (clear FAULTMASK)__STATIC_FORCEINLINE void __enable_fault_irq(void){    __ASM volatile ("cpsie f" : : : "memory");}
// Disable fault interrupts (set FAULTMASK)__STATIC_FORCEINLINE void __disable_fault_irq(void){    __ASM volatile ("cpsid f" : : : "memory");}
// Directly set the FAULTMASK value__STATIC_FORCEINLINE void __set_FAULTMASK(uint32_t faultMask){    __ASM volatile ("MSR faultmask, %0" : : "r" (faultMask) : "memory");}
// Get the FAULTMASK value__STATIC_FORCEINLINE uint32_t __get_FAULTMASK(void){    uint32_t result;
    __ASM volatile ("MRS %0, faultmask" : "=r" (result) );    return (result);}

BASEPRI

To make interrupt masking more flexible, the ARMv7-M architecture also supports BASEPRI, which masks exceptions or interrupts based on priority levels. The width of BASEPRI depends on the number of priority levels implemented in the design (as shown in the diagram above, up to 8 bits are used, but actual chip manufacturers typically use 3 or 4 bits), which is determined by the microcontroller supplier. Most Cortex-M3 or Cortex-M4 microcontrollers have 8 (3-bit wide) or 16 programmable exception priorities, in which case the width of BASEPRI is correspondingly 3 or 4 bits.

BASEPRI does not take effect when it is 0; when set to a non-zero value, it masks exceptions (including interrupts) with the same or lower priority, while higher priority ones can still be accepted by the processor. BASEPRI is also only accessible in privileged mode, and CMSIS-Core provides C language functions to read and write this register:

/* In some applications, __enable_irq() and __disable_irq() are also implemented based on BASEPRI */
// Set the value of BASEPRI__STATIC_FORCEINLINE void __set_PRIMASK(uint32_t priMask){    __ASM volatile ("MSR primask, %0" : : "r" (priMask) : "memory");}
// Get the value of BASEPRI__STATIC_FORCEINLINE uint32_t __get_BASEPRI(void){    uint32_t result;
    __ASM volatile ("MRS %0, basepri" : "=r" (result) );    return (result);}

In certain RTOS (such as FreeRTOS), BASEPRI is often used for more granular interrupt masking, retaining the execution rights of certain more important interrupts instead of directly using PRIMASK to disable all interrupts. For example, in FreeRTOS:

// Enable interrupts#define portENABLE_INTERRUPTS()    __set_BASEPRI( 0 )
// Disable interrupts// Actually masks interrupts with a priority lower than configMAX_SYSCALL_INTERRUPT_PRIORITY, // while retaining the execution rights of higher priority interrupts#define portDISABLE_INTERRUPTS()                           \{                                                          \    __set_BASEPRI( configMAX_SYSCALL_INTERRUPT_PRIORITY ); \    __DSB();                                               \    __ISB();                                               \}

The standard FreeRTOS default task is in privileged mode, so it can directly use the above API to control the switching of interrupts. If using FreeRTOS-MPU, tasks are divided into privileged and non-privileged types, where only privileged tasks can use it.

Leave a Comment