Chapter 12

Memory Protection Unit

Abstract

This chapter introduces the Memory Protection Unit (MPU), an optional programmable unit in the Cortex®-M0+ processor, including its usages, the programmer's model, the configuration steps, and the differences between the MPU in ARMv6-M and ARMv7-M processors.

Keywords

Comparison of MPU with ARMv7-M architecture; Memory barrier; Memory protection unit (MPU) overview; MPU configuration; MPU registers; MPU usages; Sub-Region Disable

12.1. What is MPU?

The Memory Protection Unit (MPU) is a programmable block inside the processor that defines memory attributes (e.g., cacheable, bufferable, see Section 7.8) and memory access permissions. It is an optional feature for the Cortex®-M0+, Cortex-M3, Cortex-M4, and Cortex-M7 processors, but is not available on the Cortex-M0 processor. As it is optional, some of the Cortex-M0+ microcontrollers have the MPU feature (e.g., the STM32L053 microcontroller used in the STM32L0 Discovery board) and some do not (to reduce silicon area and power consumption).
Unlike most other features, the MPU does not bring performance gains to embedded applications. MPU is used to detect problems in the system (e.g., when an application task behaves erroneously by trying to access a memory location which is invalid or disallowed). If a problem is detected, the HardFault exception is triggered. If the application is working perfectly, the MPU should never trigger any fault exception. In fact, many of the microcontroller applications do not need MPU.
However, as we know it, things can go wrong from time to time. In those cases, the MPU can be used to make an embedded system more robust, and in some cases make the system more secure by:
• Preventing application tasks from corrupting stack or data memory used by other tasks and the OS kernel,
• Preventing unprivileged tasks from accessing certain peripherals that can be critical to the reliability or security of the system,
• Defining SRAM or RAM space as nonexecutable (eXecute Never, XN) to prevent code injection attacks.
You can also use the MPU to define other memory attributes such as “cacheable” which can be exported to system level cache unit or memory controllers. These system level components can then make use of the memory attribute information to decide how a memory access should be handled.
By default, the MPU is disabled, and the memory access permission and memory attributes are defined by the default memory map as outlined in Chapter 7. The same applies to Cortex-M processors without MPU. In such case, the default memory attributes would be used.
The MPU contains a number of configuration registers, and these registers must be programmed to define memory regions and the MPU must be enabled before being used. If the MPU is not enabled, the behavior of the processor is the same as though no MPU is present.

12.2. MPU Use Cases

You might wonder—do I need to use the MPU in my applications?
Simple/beginner's project—If you are creating simple I/O control applications, or if you are a beginner starting to learn microcontroller programming, it is unlikely that you should need to use the MPU in your project unless the microcontroller device you are using have system level cache and need the MPU to define cache behaviors.
Internet of things—If you are creating Internet-related applications, or application that can be exposed to an untrusted communication interface, the MPU can be useful to help improve the security. For example, by defining memory ranges that are used as communication buffers as nonexecutable address spaces to prevent code injection attacks.
Industrial control applications—If you are creating applications that need to have high reliability, the MPU is very useful for defining stack restrictions in a multitasking system, and to detect unexpected faults (e.g., detection of unexpected accesses to certain memory spaces).
Automotive applications—the MPU is commonly used in the automotive segment. In some of the commonly used automotive certification processes, e.g., ISO26262, it is essential to demonstrate that software elements does not interfere each other, and therefore the MPU is needed to handle memory partitioning.
We can classify the MPU usages into a range of use cases.
Security management
• Software components that are not trusted, or have a higher risk of being compromised should be executed in unprivileged level, and the MPU can be used to restrict the memory spaces that these components can access to. The memory access permissions can also be applied to peripherals.
• RAM spaces that are used as communication buffers can contain malicious code injected through communication interface. MPU can be used to define these memory spaces as nonexecutable.
System reliability
• In a multitasking system, the MPU can be used to define the valid memory space for the stack of an application task. If an application task malfunctioned and consumed more stack space than it should, the MPU can limit the stack usage so that the task will not be able to corrupt stack space used by other application tasks or the OS data.
• In systems without embedded OS, the MPU can be used to define a non-accessible memory space at the end of the stack memory space so that a stack overflow can be detected.
• In applications that have high functional safety requirements, the MPU can be used for memory partitioning to ensure software components cannot affect each other. For example, an application task running in unprivileged state cannot corrupt data or stack used by the OS or other tasks.
• Some applications might copy program code into SRAM for execution, or copy the vector table in the SRAM for faster access. After the program code or vector table has been copied, the memory space can be defined as read only to prevent these memory spaces getting changed accidentally.
Memory attributes management
• You can use the MPU to define which memory space should be cached, and the cache behavior (e.g., write-through vs write-back).
• You can use the MPU setting to override the default memory types for certain memory space.
Note: The MPU settings only affect the access right of program code running on the same processor. In a multiprocessor system, MPU settings on one processor do not affect the access right of another processor.
Some of the embedded OS have built-in support for the MPU. In such case, the MPU configuration can be switched dynamically each time the OS switches context. So, different application tasks can have different MPU configurations.
For systems that do not use any embedded OS or if the embedded OS used does not support the MPU, the MPU can still be used with a static configuration.
In practice, it is not always possibly to completely isolate the memory space of each software components. For example, many of the runtime library functions could be shared, and data variables are placed together if the software components are compiled together. However, stack spaces of different application tasks can be separated easily and stack protection is often critical in applications that require functional safety.

12.3. Technical Introduction

The MPU works by defining a number of memory regions and restrict the memory accesses into these regions. The restrictions apply to both data and instruction accesses when the MPU is enabled. If the processor tries to access to a memory location not covered with a defined memory region, or if the access violated the memory access permission set by the memory region, the HardFault exception would be triggered and the access would be blocked before the access reach the memory system. The HardFault exception handler can then decide what to do next, for example, if the system should be reset or just terminate the offending task in an OS environment.
The MPU in the Cortex®-M0+ processor supports up to eight programmable memory regions and an optional background region. Each programmable region can have its own:
• starting addresses,
• sizes, and
• settings (memory attributes, access permissions).
Some of the details for the MPU in the Cortex-M0+ are the same as in the MPU in the Cortex-M3 and Cortex-M4 processors, which also support eight programmable regions. The MPU in the Cortex-M7 processor can support 8 or 16 regions, depending on the choice of the chip designers. Details about the comparisons of the MPU are covered in Section 12.9.
In ARMv6-M and ARMv7-M architectures, MPU regions can be overlapped. If a memory location falls in two programmed MPU regions, the memory access attributes and permission will be based on the highest-numbered region. For example, if a transfer address is within the address range defined for region 1 and region 4, the region 4 settings will be used.
By default, the MPU access permissions are bypassed when the processor is running Non-Maskable Interrupt (NMI) or HardFault handler. For example, the MPU might be used as a mechanism to detect stack limit by allocating a small SRAM space at the bottom of the stack as non accessible. When the stack limit is reached, the HardFault handler can bypass the MPU restriction and utilize the reserved SRAM space for fault handling.

12.4. MPU Registers

The MPU contains a number of memory mapped registers. These registers are located in the System Control Space (SCS). The CMSIS-CORE header file has defined a data structure for MPU registers to allow them to be accessed easily. A summary of these registers is shown in Table 12.1.

Table 12.1

Summary of the MPU registers

AddressesRegistersCMSIS-CORE symbolFunctions
0xE000ED90MPU Type RegisterMPU->TYPEProvides information about the MPU
0xE000ED94MPU Control RegisterMPU->CTRLMPU enable/disable and background region control
0xE000ED98MPU Region Number RegisterMPU->RNRSelect which MPU region to be configured
0xE000ED9CMPU Region Base Address RegisterMPU->RBARDefines base address of a MPU region
0xE000EDA0MPU Region Base Attribute and Size RegisterMPU->RASRDefines size and attributes of a MPU region

image

As in other registers in the SCS, the MPU registers are privileged accesses only. They prevent the unprivileged programs to bypass the security management imposed using MPU.
In ARM® ARMv6-M architecture, the MPU registers can be accessed by 32-bit memory access instructions only.

12.4.1. MPU Type Register

The first register is the MPU Type register. The MPU Type register can be used to determine whether the MPU is fitted. If the DREGION field is read as 0, the MPU is not implemented (see Table 12.2).

Table 12.2

MPU Type Register (MPU->TYPE, 0xE000ED90)

BitsNameTypeReset valueDescription
23:16IREGIONR0Number of instruction regions supported by this MPU; because ARMv6-M architecture uses a unified MPU, this is always 0.
15:8DREGIONR0 or 8Number of regions supported by this MPU; in the Cortex®-M0+ processors, this is either 0 (MPU not present) or 8 (MPU present).
0SEPARATER0This is always 0 as the MPU is unified.

image

12.4.2. MPU Control Register

The MPU is controlled by a number of registers. The first one is the MPU Control Register (see Table 12.3). This register has three control bits. After reset, the reset value of this register is zero, which disables the MPU. To enable the MPU, the software should first set up the settings for each MPU regions, and then set the ENABLE bit in the MPU Control Register.
The PRIVDEFENA bit in the MPU Control Register is used to enable the background region (region “minus 1”). By using PRIVDEFENA and if no other regions are set up, privileged programs will be able to access all memory locations, and only unprivileged programs will be blocked. However, if other MPU regions are programmed and enabled, they can override the background region. For example, for two systems with similar region setups but only one with PRIVDEFENA set to 1 (the right-hand side in Figure 12.1), the one with PRIVDEFENA set to one will allow privileged access to background regions.
The HFNMIENA is used to define the behavior of the MPU during execution of NMI, HardFault handlers, or when FAULTMASK is set. By default, the MPU is bypassed (disabled) in these cases. This allows the HardFault handler and the NMI Handler to execute even if the MPU was set up incorrectly.
Setting the enable bit in the MPU Control Register is usually the last step in the MPU setup code. Otherwise, the MPU might generate faults accidentally before the region configuration is done. In many cases, especially in embedded OS with dynamic MPU configurations, the MPU should be disabled at the start of the MPU configuration routine to make sure that the HardFault will not be triggered accidentally during configuration of MPU regions.

Table 12.3

MPU Control Register (MPU->CTRL, 0xE000ED94)

BitsNameTypeReset valueDescription
2PRIVDEFENAR/W0Privileged default memory map enable. When set to 1 and if the MPU is enabled, the default memory map will be used for privileged accesses as a background region. If this bit is not set, the background region is disabled and any access not covered by any enabled region will cause a fault.
1HFNMIENAR/W0If set to 1, it enables the MPU during the HardFault handler and NMI handler; otherwise, the MPU is not enabled for the HardFault handler and NMI.
0ENABLER/W0Enables the MPU if set to 1.

image

image
Figure 12.1 The effect of the PRIVDEFENA bit (background region enable).

12.4.3. MPU Region Number Register

The next MPU Control Register is the MPU Region Number register (see Table 12.4), before each region is set up, write to this register to select the region to be programmed.

Table 12.4

MPU Region Number Register (MPU->RNR, 0xE000ED98)

BitsNameTypeReset valueDescription
7:0REGIONR/WSelect the region that is being programmed. Since eight regions are supported in the MPU, only bit[2:0] of this register is implemented.

image

12.4.4. MPU Region Base Address Register

The starting address of each region is defined by the MPU Region Base Address register (see Table 12.5). Using the VALID and REGION fields in this register, we can skip the step of programming the MPU Region Number register. This can reduce the complexity of the program code, especially if the whole MPU setup is defined in a lookup table.

Table 12.5

MPU Region Base Address Register (MPU->RBAR, 0xE000ED9C)

BitsNameTypeReset valueDescription
31:NADDRR/WBase address of the region; N is dependent on the region size—for example, a 64-kB size region will have a base address field of [31:16].
4VALIDR/WIf this is 1, the REGION defined in bit[3:0] will be used in this programming step; otherwise, the region selected by the MPU Region Number register is used.
3:0REGIONR/WThis field overrides the MPU Region Number register if VALID is 1; otherwise it is ignored. Since eight regions are supported in the Cortex®-M3 and Cortex-M4 MPU, the region number override is ignored if the value of the REGION field is larger than 7.

image

12.4.5. MPU Region Base Attribute and Size Register

The properties of each region also need to be defined. This is controlled by the MPU Region Base Attribute and Size register (see Table 12.6).

Table 12.6

MPU Region Base Attribute and Size Register (MPU->RASR, 0xE000EDA0)

BitsNameTypeReset valueDescription
31:29Reserved
28XNR/WInstruction Access Disable (1 = Disable instruction fetch from this region; an attempt to do so will result in a memory management fault)
27Reserved
26:24APR/WData Access Permission field
23:22Reserved
21:19TEXR/WType Extension field—always 0 in ARMv6-M
18SR/WShareable
17CR/WCacheable
16BR/WBufferable
15:8SRDR/WSub-Region Disable
7:6Reserved
5:1REGION SIZER/WMPU Protection Region size
0ENABLER/WRegion enable

image

The REGION SIZE field (5 bits) in the MPU Region Base Attribute and Size register determines the size of the region (see Table 12.7).
The Sub-Region Disable field (bit[15:8] of the MPU Region Base Attribute and Size register) is used to divide a region into eight equal subregions and then to define each as enabled or disabled. If a subregion is disabled and overlaps another region, the access rules for the other region are applied. If the subregion is disabled and does not overlap any other region, access to this memory range will result in a HardFault exception.

Table 12.7

Encoding of REGION SIZE field for different memory region sizes

REGION sizeSizeREGION sizeSize
b00000Reservedb10000128 KB
b00001Reservedb10001256 KB
b00010Reservedb10010512 KB
b00011Reservedb100111 MB
b00100Reservedb101002 MB
b00101Reservedb101014 MB
b00110Reservedb101108 MB
b00111256 byteb1011116 MB
b01000512 byteb1100032 MB
b010011 KBb1100164 MB
b010102 KBb11010128 MB
b010114 KBb11011256 MB
b011008 KBb11100512 MB
b0110116 KBb111011 GB
b0111032 KBb111102 GB
b0111164 KBb111114 GB

image

The data Access Permission (AP) field (bit[26:24]) defines the AP of the region (see Table 12.8).

Table 12.8

Encoding of AP field for various access permission configurations

AP ValuePrivileged accessUser accessDescription
000No accessNo accessNo access
001Read/WriteNo accessPrivileged access only
010Read/WriteRead onlyWrite in a user program generates a fault
011Read/WriteRead/WriteFull access
100UnpredictableUnpredictableUnpredictable
101Read onlyNo accessPrivileged read only
110Read onlyRead onlyRead only
111Read onlyRead onlyRead only

image

The XN (Execute Never) field (bit[28]) decides whether an instruction fetch from this region is allowed. When this field is set to 1, all instructions fetched from this region will generate a HardFault exception when they enter the execution stage.
The TEX (Type Extension), S (Shareable), B (Bufferable), and C (Cacheable) fields (bit[21:16]) are more complex. These memory attributes are exported to the bus system together with each instruction and data accesses, and the information can be used by the bus system such as write buffers or cache units, as shown in Figure 12.2.
image
Figure 12.2 Memory attributes can be exported to system-level components like L2 cache and memory controller.
Although the Cortex®-M0+ processor do not include cache controllers, the implementation follows the ARMv6-M architecture, which can support external cache controllers on the system bus level, including advanced memory systems with caching capabilities. Therefore, the region access properties S, B, and C fields should be programmed correctly to support different types of memory or devices. The definition of these bit fields are shown in Table 12.9. There is also a TEX field which enables two levels of cache attributes. However, this is not supported in ARMv6-M architecture and therefore is always set to 0 in the Cortex-M0+ processor.

Table 12.9

Memory attributes (TEX is always 0 in ARMv6-M architecture)

TEXCBDescriptionRegion shareability
b00000Strongly ordered (transfers carry out and complete in programmed order)Shareable
b00001Shared device (write can be buffered)Shareable
b00010Outer and inner write-through; no write allocate[S]
b00011Outer and inner write-back; no write allocate[S]
b00100Outer and inner non-cacheable (not supported)[S]
b00101ReservedReserved
b00110Implementation defined (not supported)
b00111Outer and inner write-back; write and read allocate (not supported)[S]
b01000Nonshared device (not supported)Not shared
b01001ReservedReserved
b0101XReservedReserved
b1BBAACached memory; BB = outer policy, AA = inner policy (not supported)[S]

image

Note: [S] indicates that shareability is determined by the S-bit field (shared by multiple processors).

However, in many microcontrollers, these memory attributes are not used by the bus system and only the B (Bufferable) attribute affects the write buffer in some of the peripheral bus bridge designs.
If the microcontroller device you use supports cache, you will need to set up the memory attributes correctly based on the type of memory or devices in the memory regions. In most cases, the memory attributes can be configured as shown in Table 12.10.

Table 12.10

Commonly used memory attributes in microcontrollers

TypeMemory typeCommonly used memory attributes
ROM, flash (program memories)Normal memoryNonshareable, write-through
C = 1, B = 0, TEX = 0, S = 0
Internal SRAMNormal memoryShareable, write-through
C = 1, B = 0, TEX = 0, S = 1/S = 0
External RAMNormal memoryShareable, write-back
C = 1, B = 1, TEX = 0, S = 1/S = 0
PeripheralsDeviceShareable devices
C = 0, B = 1, TEX = 0, S = 1/S = 0
The shareable attribute is important for multiprocessor systems with caches. In these systems, if a transfer is marked as shareable, then the cache system might need to do extra work to ensure data coherency between the caches for different processors (Figure 12.3). In single processor systems, the shareable attribute is normally not used.
image
Figure 12.3 Cache coherency in multiprocessor systems need shareable attribute.

12.5. Setting Up the MPU

Most simple applications do not require MPU. By default, the MPU is disabled and the system works as if the MPU is not present. Before using the MPU, you need to work out what memory regions the program or application tasks need to (and are allowed to) access.
• Program code for privileged applications including handlers and OS kernel, typically privileged accesses only.
• Data memory including stack for privileged applications including handlers and OS kernel, typically privileged accesses only.
• Program code for unprivileged applications (application tasks), full access.
• Data memory including stack for unprivileged applications (application tasks), full accesses.
• Peripherals that are for privileged applications including handlers and OS kernel, privileged accesses only.
• Peripherals that can be used by unprivileged applications (application tasks), full accesses.
The MPU is designed to be optimized for minimum silicon size and minimum power. As a result, there are some restrictions on the memory region configurations:
• The size of the memory region must be a power of 2, ranges from 256 bytes to 4 GB.
• The starting address of a memory region must be aligned to an integer multiple value of the region size.
When defining the address and size of the memory region, one must be aware of these two restrictions. For example, if the region size is 4 KB (0x1000), the starting address must be “N x 0x1000” where N is an integer (see Figure 12.4).
image
Figure 12.4 Memory Protection Unit region addresses must be aligned to integer multiplication of the region sizes.
If the goal for using the MPU is to prevent unprivileged tasks from accessing certain memory regions, the background region feature is very useful as it reduces the setup steps required. You only need to set up the region setting for unprivileged tasks, and privileged tasks and handlers have full access to other memory spaces using the background region.
There is no need to set up memory regions for Private Peripheral Bus (PPB) address ranges (including SCS) and the Vector table. Accesses to PPB (including MPU, NVIC, SysTick, ITM) are always allowed in privileged state, and vector fetches are always permitted by the MPU.
The HardFault handler (void HardFault_Handler(void)) should always be defined if you are going to use the MPU.
By default, the vector table in startup code should contain the exception vector definition for the HardFault handler. If you are using vector table relocation feature, you might need to ensure that the vector table is set up accordingly. More information about using fault handlers is covered in Chapter 11.
To help setting the MPU, we define a number of constant values:
#define MPU_DEFS_RASR_SIZE_256B  (0x07 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_512B  (0x08 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_1KB   (0x09 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_2KB   (0x0A << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_4KB   (0x0B << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_8KB   (0x0C << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_16KB  (0x0D << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_32KB  (0x0E << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_64KB  (0x0F << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_128KB (0x10 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_256KB (0x11 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_512KB (0x12 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_1MB   (0x13 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_2MB   (0x14 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_4MB   (0x15 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_8MB   (0x16 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_16MB  (0x17 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_32MB  (0x18 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_64MB  (0x19 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_128MB (0x1A << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_256MB (0x1B << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_512MB (0x1C << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_1GB   (0x1D << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_2GB   (0x1E << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_4GB   (0x1F << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASE_AP_NO_ACCESS       (0x0 << MPU_RASR_AP_Pos)
#define MPU_DEFS_RASE_AP_PRIV_RW         (0x1 << MPU_RASR_AP_Pos)
#define MPU_DEFS_RASE_AP_PRIV_RW_USER_RO (0x2 << MPU_RASR_AP_Pos)
#define MPU_DEFS_RASE_AP_FULL_ACCESS     (0x3 << MPU_RASR_AP_Pos)
#define MPU_DEFS_RASE_AP_PRIV_RO         (0x5 << MPU_RASR_AP_Pos)
#define MPU_DEFS_RASE_AP_RO              (0x6 << MPU_RASR_AP_Pos)
#define MPU_DEFS_NORMAL_MEMORY_WT        (MPU_RASR_C_Msk)
#define MPU_DEFS_NORMAL_MEMORY_WB        (MPU_RASR_C_Msk | MPU_RASR_B_Msk)
#define MPU_DEFS_NORMAL_SHARED_MEMORY_WT (MPU_RASR_C_Msk | MPU_RASR_S_Msk)
#define MPU_DEFS_NORMAL_SHARED_MEMORY_WB (MPU_DEFS_NORMAL_MEMORY_WB | MPU_RASR_S_Msk)
#define MPU_DEFS_SHARED_DEVICE           (MPU_RASR_B_Msk)
#define MPU_DEFS_STRONGLY_ORDERED_DEVICE (0x0)
For a simple case of only four required regions, the MPU setup code can be written as a simple loop, with the configuration for the MPU->RBAR and MPU->RASR coded as a constant table:
// -------------------------------------------------------------------------
int mpu_setup(void)
{
  uint32_t i;
  uint32_t const mpu_cfg_rbar[4] = {
      0x08000000,            // Flash address for STM32L0
      0x20000000,     // SRAM
      IOPPERIPH_BASE, // GPIO base address
      USART1_BASE     // USART base address
   };
  uint32_t const mpu_cfg_rasr[4] = {
     (MPU_DEFS_RASR_SIZE_64KB     | MPU_DEFS_NORMAL_MEMORY_WT   |
       MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk),  //  Flash
     (MPU_DEFS_RASR_SIZE_8KB     | MPU_DEFS_NORMAL_MEMORY_WT   |
       MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk),  //  SRAM
     (MPU_DEFS_RASR_SIZE_4KB     | MPU_DEFS_SHARED_DEVICE         |
   MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk),  //  GPIO A to GPIO D
     (MPU_DEFS_RASR_SIZE_2KB     | MPU_DEFS_SHARED_DEVICE         |
   MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk)    //  USART
  };
  if (MPU->TYPE==0) {return 1;}    // NO MPU: Return 1 to indicate error
  __DMB();                         // Make sure outstanding transfers are done
  MPU->CTRL = 0;                   // Disable the MPU
  for (i=0;i<4;i++) {              // Configure only 4 regions
    MPU->RNR  = i;                 // Select which MPU region to configure
    MPU->RBAR = mpu_cfg_rbar[i];   // Configure region base address register
    MPU->RASR = mpu_cfg_rasr[i];   // Configure region attribute and size register
    }
  for (i=4;i<8;i++) {// Disabled unused regions
    MPU->RNR  = i;   // Select which MPU region to configure
    MPU->RBAR = 0;   // Configure region base address register
    MPU->RASR = 0;   // Configure region attribute and size register
    }
  MPU->CTRL = MPU_CTRL_ENABLE_Msk; // Enable the MPU
  __DSB();  // Memory barriers to ensure subsequence data & instruction
  __ISB();  //  transfers using updated MPU settings
  return 0; // No error
}
// -------------------------------------------------------------------------
A simple check was added in the beginning of the function to detect if the MPU is present. If the MPU is not available, the function exits with a value of 1 to indicate the error. Otherwise it returns 0 to indicate successful operations.
The example code also programs unused MPU regions to make sure that unused MPU regions are disabled. This is important for systems that configure MPU dynamically because an unused region could have been programmed to be enabled previously.
The flow for this simple MPU setup function is illustrated by Figure 12.5.
image
Figure 12.5 Example steps to set up the Memory Protection Unit (MPU).
To simplify the operation, the selection of MPU region to be programmed can be merged into the programming of MPU->RBAR, as shown in the following code:
// -------------------------------------------------------------------------
int mpu_setup(void)
{
  uint32_t i;
  uint32_t const mpu_cfg_rbar[4] = {
    // Flash address for STM32L0
    (0x08000000| MPU_RBAR_VALID_Msk     | (MPU_RBAR_REGION_Msk & 0)),
    // SRAM
    (0x20000000| MPU_RBAR_VALID_Msk     | (MPU_RBAR_REGION_Msk & 1)),
    // GPIO base address
    (IOPPERIPH_BASE| MPU_RBAR_VALID_Msk | (MPU_RBAR_REGION_Msk & 2)),
    // USART base address
    (USART1_BASE| MPU_RBAR_VALID_Msk    | (MPU_RBAR_REGION_Msk & 3))
    };
  uint32_t const mpu_cfg_rasr[4] = {
    (MPU_DEFS_RASR_SIZE_64KB      | MPU_DEFS_NORMAL_MEMORY_WT |
     MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk), // Flash
    (MPU_DEFS_RASR_SIZE_8KB       | MPU_DEFS_NORMAL_MEMORY_WT |
     MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk), // SRAM
    (MPU_DEFS_RASR_SIZE_4KB       | MPU_DEFS_SHARED_DEVICE    |
     MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk), // GPIO A to GPIO D
    (MPU_DEFS_RASR_SIZE_2KB       | MPU_DEFS_SHARED_DEVICE    |
     MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk)  // USART
    };
  if (MPU->TYPE==0) {return 1;}    // Return 1 to indicate error
  __DMB();                         // Make sure outstanding transfers are done
  MPU->CTRL = 0;                   // Disable the MPU
  for (i=0;i<4;i++) {              // Configure only 4 regions
    MPU->RBAR = mpu_cfg_rbar[i];   // Configure region base address register
    MPU->RASR = mpu_cfg_rasr[i];   // Configure region attribute and size register
    }
  for (i=4;i<8;i++) {// Disabled unused regions
    MPU->RNR  = i;   // Select which MPU region to configure
    MPU->RBAR = 0;   // Configure region base address register
    MPU->RASR = 0;   // Configure region attribute and size register
    }
  MPU->CTRL = MPU_CTRL_ENABLE_Msk; // Enable the MPU
  __DSB();  // Memory barriers to ensure subsequence data & instruction
  __ISB();  //  transfers using updated MPU settings
  return 0; // No error
}
// -------------------------------------------------------------------------
These configuration methods shown so far assume that we know the required settings in advance. If not, we might need to create some generic functions to make the MPU configuration easier. For example, we can create the following C functions:
// -------------------------------------------------------------------------
// Enable MPU with input options
// Options can be MPU_CTRL_HFNMIENA_Msk or MPU_CTRL_PRIVDEFENA_Msk
void mpu_enable(uint32_t options)
{
  MPU->CTRL = MPU_CTRL_ENABLE_Msk | options;   // Disable the MPU
  __DSB();  // Ensure MPU settings take effects
  __ISB();  // Sequence instruction fetches using update settings
  return;
}
// Disable the MPU.
void mpu_disable(void)
{
  __DMB();       // Make sure outstanding transfers are done
  MPU->CTRL = 0; // Disable the MPU
  return;
}
// Function to disable a region (0 to 7)
void mpu_region_disable(uint32_t region_num)
{
  MPU->RNR  = region_num;
  MPU->RBAR = 0;
  MPU->RASR = 0;
  return;
}
// Function to enable a region
void mpu_region_config(uint32_t region_num, uint32_t addr, uint32_t size, uint32_t attributes)
{
  MPU->RNR  = region_num;
  MPU->RBAR = addr;
  MPU->RASR = size | attributes;
  return;
}
After these functions are created, we can configure the MPU using these functions:
int mpu_setup(void)
{
  if (MPU->TYPE==0) {return 1;}    // NO MPU: Return 1 to indicate error
  mpu_disable();
  mpu_region_config(0, 0x08000000, MPU_DEFS_RASR_SIZE_64KB,
    MPU_DEFS_NORMAL_MEMORY_WT | MPU_DEFS_RASE_AP_FULL_ACCESS  |
    MPU_RASR_ENABLE_Msk), // Region 0 - Flash
  mpu_region_config(1, 0x20000000, MPU_DEFS_RASR_SIZE_8KB,
    MPU_DEFS_NORMAL_MEMORY_WT | MPU_DEFS_RASE_AP_FULL_ACCESS  |
    MPU_RASR_ENABLE_Msk), // Region 1 - SRAM
  mpu_region_config(2, IOPPERIPH_BASE, MPU_DEFS_RASR_SIZE_4KB,
    MPU_DEFS_SHARED_DEVICE    | MPU_DEFS_RASE_AP_FULL_ACCESS  |
    MPU_RASR_ENABLE_Msk), // Region 2 - GPIO A to GPIO D
  mpu_region_config(3, USART1_BASE, MPU_DEFS_RASR_SIZE_2KB,
    MPU_DEFS_SHARED_DEVICE    | MPU_DEFS_RASE_AP_FULL_ACCESS  |
    MPU_RASR_ENABLE_Msk), // Region 3 - USART
  mpu_region_disable(4);// Disabled unused regions
  mpu_region_disable(5);
  mpu_region_disable(6);
  mpu_region_disable(7);
  mpu_enable(0); // Enable the MPU with no additional option
  return 0; // No error
}

12.6. Memory Barrier and MPU Configuration

In the examples shown, we have added a number of memory barrier instructions in the MPU configuration code.
• Data Memory Barrier (DMB). This is used before disabling the MPU to ensure that there is no reordering of data transfers and if there is any outstanding transfer, we wait until the transfer is completed before writing to the MPU Control Register (MPU->CTRL) to disable the MPU.
• Data Synchronization Barrier (DSB). This is used after enabling the MPU to ensure that the subsequent ISB instruction is executed only after the write to the MPU Control Register is completed. This also ensures all subsequent data transfers use the new MPU settings.
• Instruction Synchronization Barrier (ISB). This is used after the DSB to ensure the processor pipeline is flushed and subsequent instructions are refetched again with updated MPU settings.
The use of these memory barriers are based on architecture recommendations. Omitting these memory barriers on the Cortex®-M0+ processor rarely causes any failure due to simple nature of the processor pipeline: the processor can only handle one data transfer at any time. The only case where an ISB is really needed is when the MPU settings are updated and the subsequent instruction access can only be carried out using the new MPU settings.
However, from software portability point of view, these memory barriers are important because it allows the software to be reused on all Cortex-M processors.
If the MPU is used by an embedded OS and the MPU configuration is done inside the context switching operation, which is typically within the PendSV exception handler, the ISB instruction is not required from architecture point of view because the exception entrance and exit sequence also has the ISB effect.
Additional information about the use of memory barriers on the Cortex-M processors can be found on ARM® application note 321, A Programmer Guide to the Memory Barrier instruction for ARM Cortex-M Family Processor (reference 8).

12.7. Using Sub-Region Disable

The Sub-Region Disable (SRD) feature is used to divide an MPU region into eight equal parts and set each of them enabled or disabled individually. This feature can be used in a number of ways:

12.7.1. Allow Efficient Memory Separation

The SRD enables more efficient memory usage while allowing protection to be implemented. For example, assumed that task A needs 5 KB of stack and task B needs 3 KB of stack, and the MPU is used to separate the stack space, the memory arrangement without SRD feature will need 8 KB for task A's stack and 4 KB for task B's stack, as shown in Figure 12.6.
image
Figure 12.6 Without Sub-Region Disable, more memory space could be wasted because of region size and alignment requirements.
With the SRD, we can reduce the memory usage by overlapping the two memory regions, and use SRD to prevent the application task to access the other task's stack space, as shown in Figure 12.7.
image
Figure 12.7 With Sub-Region Disable (SRD), regions can be overlapped but still separated for better memory usage efficiency.

12.7.2. Reduce the Total Number of Regions Needed

When defining peripheral access permissions, very often you might find that some peripherals need to be accessed by unprivileged tasks and some must be protected and have to be privileged access only. To implement the protection without SRD, we might need to use a large number of regions.
Since the peripherals usually have the same address size, we can easily apply SRD to define the access permissions. For example, we can define a region (or use the background region feature) to enable privileged accesses to all peripherals. Then define a higher numbered region which overlapped the peripheral address space as FULL ACCESS (accessible by unprivileged task), and use SRD to mask out the peripherals that has privileged access only. A simple illustration is shown in Figure 12.8.

12.8. Considerations When Using MPU

A number of aspects need to be considered when using the MPU. In many cases, when the MPU is used with an embedded OS, it is highly desirable to have MPU support built-in with the OS being used. For example, a special version of FreeRTOS (called FreeRTOS-MPU, www.freertos.org), and the OpenRTOS from Wittenstein High Integrity Systems (www.highintegritysystems.com) can make use of the MPU features. It is also possible to use the MPU with a static configuration with other RTOS, and use the stack limit detection feature for stack overflow detection.
image
Figure 12.8 Using Sub-Region Disable to control access right to separate peripherals.

12.8.1. Program Code

In most cases, it can be difficult to isolate the program memory into different MPU regions for different tasks because the tasks can share various functions, including runtime library functions and device driver library functions. Also, if the application tasks and the OS are compiled together, it can be difficult to have clear and well-aligned address boundaries between each of the application tasks and the OS kernel, which is needed for setting up the MPU regions. Typically the program memory (e.g., flash) can be defined as just one region, and might be configured with read only access permission.

12.8.2. Data Memory

If the application tasks and OS are compiled together in one go, it is likely that some of the data used by the application tasks and the OS will be mixed together. It is then impossible to isolate the access permissions of individual data elements. You might need to compile the tasks separately and then use linker scripts or other methods to place the data sections in the RAM manually. However, heap memory space might be needed to be shared and cannot be protected using MPU.
Isolation of stack memory is usually easier to handle. You can reserve memory space in the linking stage and force the application tasks to use the reserved space for stack operations. Different embedded OS and tool chains have different ways to allocate stack spaces.

12.9. Comparing with the MPU in the Cortex®-M3/M4/M7 Processors

The optional MPU in the Cortex-M0+ processor is fairly similar to the MPU in the Cortex-M3, Cortex-M4 and Cortex-M7. There are a few differences, so if an MPU configuration software has to be used on Cortex-M0+ as well as on Cortex-M3/M4/M7 Processors, the following areas (see Table 12.11) need to be taken care of.
The MPU memory attributes in ARMv6-M only support one level of cache policy. Therefore the TEX field is always 0 in the Cortex-M0+ processor. On the ARMv7-M architecture, the TEX can be set to non-zero value and enable separated inner and outer cache schemes.
In addition, in ARMv7-M architecture, there is a configurable fault exception for handling MPU-generated fault exception called MemManage fault (Memory Management Fault), and additional fault status registers for easier diagnosis of the causes of the fault. By default, the MemManage fault is disabled so that the HardFault would still be used, but the MemManage fault can be enabled at runtime with a configurable priority level to allow more flexible fault management.

Table 12.11

Comparison of MPU features in Cortex®-M0+ processor to Cortex-M3/M4/M7 processors

ARMv6-M (Cortex-M0+)ARMv7-M (Cortex-M3/M4/M7)
Number of regions88 (all)/16 (Cortex-M7 only)
Unified I & D regionsYY
Region addressYY
Region size256 bytes to 4 GB
(can use SRD to get to 32 bytes)
32 bytes to 4 GB
Region memory attributesS, C, B, XNTEX, S, C, B, XN
Region Access Permission (AP)YY
Sub-Region Disable (SRD)8 bits8 bits
Background regionYes (programmable)Yes (programmable)
MPU bypass for NM/HardFaultYes (programmable)Yes (programmable)
Alias of MPU registersNY
MPU registers accessesWord size onlyWord/Halfword/Byte
Fault exceptionHardFault onlyHardFault/MemManage
Although the ARMv7-M architecture allows a smaller region size (down to 32 bytes), in ARMv6-M with can use the 256 byte region size with Sub-Region Disable to set 32 byte subregions. In ARMv7-M architecture sub-Region Disable cannot be used if the region size is 128 bytes or less. So you get the same effective minimum region size.
Overall, the MPUs support similar level of memory protection features, and the software porting between the two MPU types should be straight forward. However, the ARMv7-M architecture supports a range of fault status registers that help fault handlers to manage the fault events. This is not available in the ARMv6-M architecture. As a result, in most cases a HardFault event in the Cortex-M0+ processor is considered as nonrecoverable (or fatal) which require a reset or task termination, whereas in ARMv7-M architecture, it is possible to recover from some of the MPU-related fault situations.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.1.239