MPU
What’s MPU
Armv7-M/Armv8-M中支持PMSA(Protected Memory System Architecture),MPU就是其中的一个可选组件。MPU在Cortex-M中用来保护系统内存空间。它有以下功能:
- Memory region protection
- Access permissions
- Exporting memory attributes to the system
它可以用来实施特权访问规则,在处理器不同状态或者不同任务之间实现隔离,还可以设置内存的属性。后边的章节通过介绍它的寄存器来看它如何实现这些功能。
本文以Armv8-M为例。
Memory Model of Cortex-M
The Armv8-M architecture supports 32-bit memory addressing and has a 4GB linear address space. The memory space is unified, that is both instructions and data share the same address space. The default memory map or default memory address space is defined by the architecture. The default memory map divides the 4GB address range into a number of regions.
Each region within the memory address space has a set of memory attributes and access permissions. These memory attributes include the following:
Memory type
Shareability
Cacheability
如上所述,Cortex-M支持32位总共4GB的线性地址空间。
Default Memory map
系统有一个默认的memory map,如下图所示:
Cortex-M定义了一些与default memory map有关的rule:
RHBNG Memory attributes are determined from the default system address map or by using an MPU.(memory的属性由默认的系统地址映射或者MPU来决定)
RMCCL The default memory map can be configured to provide a background region for privileged accesses. (系统默认的地址映射可以设置为特权访问控制的background region)
RPBPJ When the MPU is enabled, the PE can be configured to use the default system map when it processes NMI and HardFault exceptions. (在NMI和HardFault异常发生时,即便MPU是使能的,也可以设置PE适用默认的系统映射)
RJVJC When the MPU is disabled or not present, accesses use memory attributes from the default system address map. (在MPU不存在或者禁止时,系统使用默认的memory attribute)
RRPWB Exception vector reads from the Vector Address Table always use the default system address map. (异常向量表始终使用默认系统地址映射)
RCPMW The MPU is restricted in how it can change the default memory map attributes associated with System space, that is, for addresses 0xE0000000. System space is always XN (Execute Never) and it is always Device-nGnR. If the MPU maps this to a type other than Device-nGnRnE, it is UNKNOWN whether the region is treated as Device-nGnRE or as Device-nGnRnE. (对于System Space,MPU的使用是受限的)
MPU Registers
需要注意的是,MPU的寄存器根据它的security state是banked。Security state是secure是可以访问non-secure的寄存器,但反过来则不行。
MPU_TYPE
- DREGION, bits [15:8]
MPU最多支持多少个data regions。如果值为0,则在该Security State没有实现MPU。 - SEPARATE, bit [0]
这个bit则在Armv8-M为0,因为Armv8-M上只支持unified memory,不支持单独的指令或者数据分区。
MPU_CTRL
- PRIVDEFENA, bit [2]
- 0 (Default/Reset value)禁用默认的system memory map。任何落在MPU定义的region外的访问会产生fault。
- 1 设为1则system memory map会作为privileged mode下的background region。MPU设置的region优先于background region,访问不落在MPU的region中则看background region的属性。
- HFNMIENA, bit [1]
- 0 (Default/Reset value)进入HardFault或者NMI的处理函数时,MPU不生效。
- 1 进入HardFault或者NMI的处理函数时,MPU同样生效。
- ENABLE, bit [0]
- 0 (Default/Reset value)禁用MPU(上述两个bit都无效)
- 1 使能MPU
MPU_MAIR0 and MPU_MAIR1
这个寄存器类似Armv8-A MMU系统里的MAIR_ELx。用来定义不同的memory attribute,后续把相应的attr index设给region就可以了。每个attr为8个bits,高4bit为outer,低4bit为inner。它的encoding如下:
- Outer值为0b0000,则inner只有bits[3:2]有效。(据下值,为device属性)
- 0b00 Device-nGnRnE
- 0b01 Device-nGnRE
- 0b10 Device-nGRE
- 0b11 Device-GRE
- Outer值不为0b0000,则有:
典型的值会有以下几个:
- 0b00000000 Device-nGnRnE 用于访问寄存器
- 0b01000100 Non-cacheable 可用于不同核间共享内存而不需要做cache maintain操作
- 0b01110111 Outer Write-Back Transient, read/write allocate 用于日常memory操作,速度快
MPU_RNR
这个寄存器只有一个字段叫REGION,用它来选定需要program的region,则后续的寄存器的值都用来设置该region的属性,直到这个寄存器被改写。
MPU_RBAR
- BASE, bits [31:5] 设定MPU_RNR选定region的基地址,32字节对齐。
- SH, bits [4:3] Region的Shareability
- 0b00 Non-shareable
- 0b10 Outer Shareable
- 0b11 Inner Shareable
- AP[2:1], bits [2:1] Region的Access Permission
- 0b00 Read/write by privileged code only
- 0b01 Read/write by any privilege level
- 0b10 Read-only by privileged code only
- 0b11 Read-only by any privilege level
- XN, bit [0] 能否执行代码
- 0 Execution only permitted if read permitted
- 1 Execution not permitted
典型用法是把privileged mode的代码段属性AP设为0b10,XN为0。其他段的XN则设为1。
MPU_RLAR
- LIMIT, bits [31:5] 对应BASE,设定当前region的范围。
- AttrIndx, bits [3:1] 这里选择MPU_MAIR0或MPU_MAIR1中设定的某个attr的index。
- EN, bit [0] 0:禁用该region;1:使能该region;
MPU_RBAR_An and MPU_RLAR_An
这两个寄存器分别是MPU_RBAR和MPU_RLAR的别名,它设置的region由MPU_RNR[7:2]和此处的n决定。例如:MPU_RNR[7:2] = 0b000001,写MPU_RBAR_A3的时候设置的region为7。这个寄存器的好处是不用改动MPU_RNR寄存器,也可以快速设置MPU。
Application in FreeRTOS
FreeRTOS Version: V11.10
在FreeRTOS中用宏portUSING_MPU_WRAPPERS来控制是否使用MPU。由于FreeRTOS支持动态分配或者静态分配task的memory,在FreeRTOS.h说明中,有一张表供参考:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
* +-----+---------+--------+-----------------------------+-----------------------------------+------------------+-----------+
* | MPU | Dynamic | Static | Available Functions | Possible Allocations | Both Dynamic and | Need Free |
* | | | | | | Static Possible | |
* +-----+---------+--------+-----------------------------+-----------------------------------+------------------+-----------+
* | 0 | 0 | 1 | xTaskCreateStatic | TCB - Static, Stack - Static | No | No |
* +-----|---------|--------|-----------------------------|-----------------------------------|------------------|-----------|
* | 0 | 1 | 0 | xTaskCreate | TCB - Dynamic, Stack - Dynamic | No | Yes |
* +-----|---------|--------|-----------------------------|-----------------------------------|------------------|-----------|
* | 0 | 1 | 1 | xTaskCreate, | 1. TCB - Dynamic, Stack - Dynamic | Yes | Yes |
* | | | | xTaskCreateStatic | 2. TCB - Static, Stack - Static | | |
* +-----|---------|--------|-----------------------------|-----------------------------------|------------------|-----------|
* | 1 | 0 | 1 | xTaskCreateStatic, | TCB - Static, Stack - Static | No | No |
* | | | | xTaskCreateRestrictedStatic | | | |
* +-----|---------|--------|-----------------------------|-----------------------------------|------------------|-----------|
* | 1 | 1 | 0 | xTaskCreate, | 1. TCB - Dynamic, Stack - Dynamic | Yes | Yes |
* | | | | xTaskCreateRestricted | 2. TCB - Dynamic, Stack - Static | | |
* +-----|---------|--------|-----------------------------|-----------------------------------|------------------|-----------|
* | 1 | 1 | 1 | xTaskCreate, | 1. TCB - Dynamic, Stack - Dynamic | Yes | Yes |
* | | | | xTaskCreateStatic, | 2. TCB - Dynamic, Stack - Static | | |
* | | | | xTaskCreateRestricted, | 3. TCB - Static, Stack - Static | | |
* | | | | xTaskCreateRestrictedStatic | | | |
* +-----+---------+--------+-----------------------------+-----------------------------------+------------------+-----------+
这里以portUSING_MPU_WRAPPERS为1时的xTaskCreateStatic为例。调用路径为:
- xTaskCreateStatic
- prvCreateStaticTask
- prvCreateStaticTask
- buffer信息保存在task的TCB中
- prvInitialiseNewTask
- 根据传入参数判断是否运行在priviledged mode
- 调用vPortStoreTaskMPUSettings保存MPU setting。这个函数的实现在不同的MCU上是不同的。Armv8-M的实现参考这里。
在该函数的实现里,首先定义MAIR:
1
2
3
4
5
6
7
/* Non-Transient, Write-back, Read-Allocate and Write-Allocate. */
#define portMPU_NORMAL_MEMORY_BUFFERABLE_CACHEABLE ( 0xFF )
#define portMPU_DEVICE_MEMORY_nGnRnE ( 0x00 )
/* Setup MAIR0. */
xMPUSettings->ulMAIR0 = ( ( portMPU_NORMAL_MEMORY_BUFFERABLE_CACHEABLE << portMPU_MAIR_ATTR0_POS ) & portMPU_MAIR_ATTR0_MASK );
xMPUSettings->ulMAIR0 |= ( ( portMPU_DEVICE_MEMORY_nGnRE << portMPU_MAIR_ATTR1_POS ) & portMPU_MAIR_ATTR1_MASK );
然后把诸如stack和user自定义的region都放在xMPU_SETTINGS这个结构里。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/* This function is called automatically when the task is created - in
* which case the stack region parameters will be valid. At all other
* times the stack parameters will not be valid and it is assumed that
* the stack region has already been configured. */
if( uxStackDepth > 0 )
{
ulRegionStartAddress = ( uint32_t ) pxBottomOfStack;
ulRegionEndAddress = ( uint32_t ) pxBottomOfStack + ( uxStackDepth * ( configSTACK_DEPTH_TYPE ) sizeof( StackType_t ) ) - 1;
/* If the stack is within the privileged SRAM, do not protect it
* using a separate MPU region. This is needed because privileged
* SRAM is already protected using an MPU region and ARMv8-M does
* not allow overlapping MPU regions. */
if( ( ulRegionStartAddress >= ( uint32_t ) __privileged_sram_start__ ) &&
( ulRegionEndAddress <= ( uint32_t ) __privileged_sram_end__ ) )
{
xMPUSettings->xRegionsSettings[ 0 ].ulRBAR = 0;
xMPUSettings->xRegionsSettings[ 0 ].ulRLAR = 0;
}
else
{
/* Define the region that allows access to the stack. */
ulRegionStartAddress &= portMPU_RBAR_ADDRESS_MASK;
ulRegionEndAddress &= portMPU_RLAR_ADDRESS_MASK;
xMPUSettings->xRegionsSettings[ 0 ].ulRBAR = ( ulRegionStartAddress ) |
( portMPU_REGION_NON_SHAREABLE ) |
( portMPU_REGION_READ_WRITE ) |
( portMPU_REGION_EXECUTE_NEVER );
xMPUSettings->xRegionsSettings[ 0 ].ulRLAR = ( ulRegionEndAddress ) |
( portMPU_RLAR_ATTR_INDEX0 ) |
( portMPU_RLAR_REGION_ENABLE );
}
}
/* User supplied configurable regions. */
for( ulRegionNumber = 1; ulRegionNumber <= portNUM_CONFIGURABLE_REGIONS; ulRegionNumber++ )
{
/* If xRegions is NULL i.e. the task has not specified any MPU
* region, the else part ensures that all the configurable MPU
* regions are invalidated. */
if( ( xRegions != NULL ) && ( xRegions[ lIndex ].ulLengthInBytes > 0UL ) )
{
/* Translate the generic region definition contained in xRegions
* into the ARMv8 specific MPU settings that are then stored in
* xMPUSettings. */
ulRegionStartAddress = ( ( uint32_t ) xRegions[ lIndex ].pvBaseAddress ) & portMPU_RBAR_ADDRESS_MASK;
ulRegionEndAddress = ( uint32_t ) xRegions[ lIndex ].pvBaseAddress + xRegions[ lIndex ].ulLengthInBytes - 1;
ulRegionEndAddress &= portMPU_RLAR_ADDRESS_MASK;
/* Start address. */
xMPUSettings->xRegionsSettings[ ulRegionNumber ].ulRBAR = ( ulRegionStartAddress ) |
( portMPU_REGION_NON_SHAREABLE );
/* RO/RW. */
if( ( xRegions[ lIndex ].ulParameters & tskMPU_REGION_READ_ONLY ) != 0 )
{
xMPUSettings->xRegionsSettings[ ulRegionNumber ].ulRBAR |= ( portMPU_REGION_READ_ONLY );
}
else
{
xMPUSettings->xRegionsSettings[ ulRegionNumber ].ulRBAR |= ( portMPU_REGION_READ_WRITE );
}
/* XN. */
if( ( xRegions[ lIndex ].ulParameters & tskMPU_REGION_EXECUTE_NEVER ) != 0 )
{
xMPUSettings->xRegionsSettings[ ulRegionNumber ].ulRBAR |= ( portMPU_REGION_EXECUTE_NEVER );
}
/* End Address. */
xMPUSettings->xRegionsSettings[ ulRegionNumber ].ulRLAR = ( ulRegionEndAddress ) |
( portMPU_RLAR_REGION_ENABLE );
/* Normal memory/ Device memory. */
if( ( xRegions[ lIndex ].ulParameters & tskMPU_REGION_DEVICE_MEMORY ) != 0 )
{
/* Attr1 in MAIR0 is configured as device memory. */
xMPUSettings->xRegionsSettings[ ulRegionNumber ].ulRLAR |= portMPU_RLAR_ATTR_INDEX1;
}
else
{
/* Attr0 in MAIR0 is configured as normal memory. */
xMPUSettings->xRegionsSettings[ ulRegionNumber ].ulRLAR |= portMPU_RLAR_ATTR_INDEX0;
}
}
else
{
/* Invalidate the region. */
xMPUSettings->xRegionsSettings[ ulRegionNumber ].ulRBAR = 0UL;
xMPUSettings->xRegionsSettings[ ulRegionNumber ].ulRLAR = 0UL;
}
lIndex++;
}
准备好这些setting,然后就是在task被调度的时候apply到寄存器中了。比如FreeRTOS开始task调度的时候。在function xPortStartScheduler中调用prvSetupMPU来设置task的MPU。
1
2
3
/* Enable MPU with privileged background access i.e. unmapped
* regions have privileged access. */
portMPU_CTRL_REG |= ( portMPU_PRIV_BACKGROUND_ENABLE_BIT | portMPU_ENABLE_BIT );
在函数最后设置MPU_CTRL寄存器来enable MPU,同时把background memory map enable为priviledged access,也就是在thread mode下只能访问之前设的regions。
Reference
Armv8-M Architecture Reference Manual
Armv8-M Memory Model and Memory Protection User Guide