STM32 的 Cortex M4(STM32F3/F4 系列)和 Cortex M7(STM32H7/H7 系列)系列的产品,都带有内存保护单元(memory protection unit),简称: MPU。使用 MPU 可以设置不同存储区域的存储器访问特性(如只支持特权访问或全访问)和存储器属性(如可缓存、可共享),从而提高嵌入式系统的健壮性,使系统更加安全。
简单的说就是内存保护、外设保护和代码访问保护。
MPU简介
MPU,即内存保护单元,可以设置不同存储区域的存储器访问特性(如只支持特权访问
或全访问)和存储器属性(如可缓存、可缓冲、可共享),对存储器(主要是内存和外设)提
供保护,从而提高系统可靠性:
1,阻止用户应用程序破坏操作系统使用的数据。
2,阻止一个任务访问其他任务的数据区,从而隔离任务。
3,可以把关键数据区域设置为只读,从根本上解决被破坏的可能。
4,检测意外的存储访问,如堆栈溢出,数组越界等。
5,将 SRAM 或者 RAM 空间定义为不可执行(用不执行, XN),防止代码注入攻击。
MPU功能实现
- MPU 可以配置保护 16 个内存区域(这 16 个内存域是独立配置的),每个区域最小要求 256 字节,每个区域还可以配置为 8 个子区域。由于子区域一般都相同大小,这样每个子区域的大小就是 32 字节,正好跟 Cache 的 Cache Line 大小一样。
- MPU 可以配置的 16 个内存区的序号范围是 0 到 15,还有默认区 default region,也叫作背景区,序号-1。由于这些内存区可以嵌套和重叠,所以这些区域在嵌套或者重叠的时候有个优先级的问题。序号15 的优先级最高,以此递减,序号-1,即背景区的优先级最低。这些优先级是固定的。
- 下面通过一个具体的实例来理解。如下所示共有 7 个区,背景区和序号 0-5 的区。内存区 4 跟
内存区 0 和 1 有重叠部分,那么重叠部分将按照内存区 4 的配置规则执行;内存区 5 被完全包含在内存区
3 里面,那么这部分内存区将按照内存区 5 的配置规则执行。
MPU 可以配置的三种内存类型
- Normal memory
CPU 以最高效的方式加载和存储字节、半字和字,对于这种内存区,CPU 的加载或存储不一定要按
照程序列出的顺序执行。 - Device memory
对于这种类型的内存区,加载和存储要严格按照次序进行,这样是为了确保寄存器按照正确顺序设置。 - Strongly ordered memory
程序完全按照代码顺序执行,CPU 需要等待当前的加载/存储指令执行完毕后才执行下一条指令。这
样会导致性能下降。
MPU 配置寄存器
STM32H7 的 MPU 提供多达 16 个可编程保护区域(region),每个区域(region)都有自己的可编程起始地址、大小及设置。 MPU 功能必须开启才会有效,默认条件下, MPU 是关闭的,所以,我们要向使用 MPU,必须先打开 MPU 才行。
16 个可编程保护区域(region),一般来说是足够使用的了,如果觉得不够,每个区域(region)还可以被进一步划分为更小的子区域( sub region),另外,还允许启用一个背景区域(即没有 MPU 设置的其他所有地址空间),背景区域只允许特权访问。在启用 MPU 后,就不得再访问定义之外的地址区间,也不得访问未经授权的区域(region),否则,将以“访问违例”处理,触发 MemManage 异常。
此外, MPU 定义的区域(region) 还可以相互交迭。如果某块内存落在多个区域( region)中,则访问属性和权限将由编号最大的region来决定。比如,若2号region与5号region交迭,则交迭的部分受 5 号 region 控制。MPU 设置是由 CTRL、 RNR、 RBAR 和 RASR 等寄存器控制的。
MPU_CTRL 寄存器各位描述
MPU_RNR 寄存器各位描述
MPU_RBAR 寄存器各位描述
MPU_RASR 寄存器各位描述
这个位用于控制内存区的子区域,使用的是 bit[15:8],共计 8 个 bit,一个 bit 控制一个子区域,0表示使能此子区域,1 表示禁止此子区域。
一般情况,基本不使用子区域的禁止功能,所以配置 HAL 库的 SubRegionDisable 参数时,直接取
值 0x00 即可,表示 8 个子区域均使能。
RASR 寄存器的 XN 位
XN 位,用于控制是否允许从此区域取指,如果 XN=1,说明禁止从区域取指,如果强行取指,将产生一个 MemManage 异常。如果设置 XN=0,则允许取指。
对应的 HAL 库 MPU 参数如下:
/** @defgroup CORTEX_MPU_Instruction_Access CORTEX MPU Instruction Access
* @{
*/
#define MPU_INSTRUCTION_ACCESS_ENABLE ((uint8_t)0x00) //允许指令提取,执行代码
#define MPU_INSTRUCTION_ACCESS_DISABLE ((uint8_t)0x01) //禁止执行代码
RASR 寄存器的 AP 位
AP 位,由 3 个位(bit[26:24])组成,用于控制数据的访问权限(访问许可),如下表所示
这几个参数对应的 HAL 库 MPU 参数如下:
/** @defgroup CORTEX_MPU_Region_Permission_Attributes CORTEX MPU Region Permission Attributes
* @{
*/
#define MPU_REGION_NO_ACCESS ((uint8_t)0x00)
#define MPU_REGION_PRIV_RW ((uint8_t)0x01)
#define MPU_REGION_PRIV_RW_URO ((uint8_t)0x02)
#define MPU_REGION_FULL_ACCESS ((uint8_t)0x03)
#define MPU_REGION_PRIV_RO ((uint8_t)0x05)
#define MPU_REGION_PRIV_RO_URO ((uint8_t)0x06)
RASR 寄存器的 TEX,C,B 和 S 位(重要配置选项)
TEX,C,B 和 S 的定义如下,这仅关注 TEX = 0b000 和 0b001,其它的 TEX 配置基本用不到。
TEX 用于配置 Cache 策略,支持如下四种情况,需要配合 C 和 B 位的配置才能实现。
TEX 对应的 HAL 库 MPU 参数给了三个,实际应用中仅用到前两个
MPU_TEX_LEVEL0
MPU_TEX_LEVEL1
/** @defgroup CORTEX_MPU_TEX_Levels MPU TEX Levels
* @{
*/
#define MPU_TEX_LEVEL0 ((uint8_t)0x00)
#define MPU_TEX_LEVEL1 ((uint8_t)0x01)
#define MPU_TEX_LEVEL2 ((uint8_t)0x02)
C 位对应的 HAL 库 MPU 参数如下,用于使能或者禁止 Cache。
/** @defgroup CORTEX_MPU_Access_Cacheable CORTEX MPU Instruction Access Cacheable
* @{
*/
#define MPU_ACCESS_CACHEABLE ((uint8_t)0x01)
#define MPU_ACCESS_NOT_CACHEABLE ((uint8_t)0x00)
B 位对应的 HAL 库 MPU 参数如下,用于配合 C 位实现 Cache 模式下是否使用缓冲
/** @defgroup CORTEX_MPU_Access_Bufferable CORTEX MPU Instruction Access Bufferable
* @{
*/
#define MPU_ACCESS_BUFFERABLE ((uint8_t)0x01)
#define MPU_ACCESS_NOT_BUFFERABLE ((uint8_t)0x00)
S 位对应的 HAL 库 MPU 参数如下,用于解决多总线或者多核访问的共享问题。
/** @defgroup CORTEX_MPU_Access_Shareable CORTEX MPU Instruction Access Shareable
* @{
*/
#define MPU_ACCESS_SHAREABLE ((uint8_t)0x01)
#define MPU_ACCESS_NOT_SHAREABLE ((uint8_t)0x00)
RASR 寄存器的 SIZE 位
SIZE 位使用的是 bit[5:1],共计 5 个 bit,可以表示 2^5 = 32 种大小。 对应的 HAL 库给出了可以
配置的 28 个参数:
/** @defgroup CORTEX_MPU_Region_Size CORTEX MPU Region Size
* @{
*/
#define MPU_REGION_SIZE_32B ((uint8_t)0x04)
#define MPU_REGION_SIZE_64B ((uint8_t)0x05)
#define MPU_REGION_SIZE_128B ((uint8_t)0x06)
#define MPU_REGION_SIZE_256B ((uint8_t)0x07)
#define MPU_REGION_SIZE_512B ((uint8_t)0x08)
#define MPU_REGION_SIZE_1KB ((uint8_t)0x09)
#define MPU_REGION_SIZE_2KB ((uint8_t)0x0A)
#define MPU_REGION_SIZE_4KB ((uint8_t)0x0B)
#define MPU_REGION_SIZE_8KB ((uint8_t)0x0C)
#define MPU_REGION_SIZE_16KB ((uint8_t)0x0D)
#define MPU_REGION_SIZE_32KB ((uint8_t)0x0E)
#define MPU_REGION_SIZE_64KB ((uint8_t)0x0F)
#define MPU_REGION_SIZE_128KB ((uint8_t)0x10)
#define MPU_REGION_SIZE_256KB ((uint8_t)0x11)
#define MPU_REGION_SIZE_512KB ((uint8_t)0x12)
#define MPU_REGION_SIZE_1MB ((uint8_t)0x13)
#define MPU_REGION_SIZE_2MB ((uint8_t)0x14)
#define MPU_REGION_SIZE_4MB ((uint8_t)0x15)
#define MPU_REGION_SIZE_8MB ((uint8_t)0x16)
#define MPU_REGION_SIZE_16MB ((uint8_t)0x17)
#define MPU_REGION_SIZE_32MB ((uint8_t)0x18)
#define MPU_REGION_SIZE_64MB ((uint8_t)0x19)
#define MPU_REGION_SIZE_128MB ((uint8_t)0x1A)
#define MPU_REGION_SIZE_256MB ((uint8_t)0x1B)
#define MPU_REGION_SIZE_512MB ((uint8_t)0x1C)
#define MPU_REGION_SIZE_1GB ((uint8_t)0x1D)
#define MPU_REGION_SIZE_2GB ((uint8_t)0x1E)
#define MPU_REGION_SIZE_4GB ((uint8_t)0x1F)
MPU 的配置函数
HAL 库的 stm32h7xx_hal_cortex.c 文件为 MPU 的配置提供了三个函数:
◆ HAL_MPU_Disable
◆ HAL_MPU_Enable
◆ HAL_MPU_ConfigRegion
HAL_MPU_Disable
此函数用于禁止 MPU。配置 MPU 前要优先调用此函数禁止 MPU,然后才可以配置。
void HAL_MPU_Disable(void)
{
/* Make sure outstanding transfers are done */
__DMB();
/* Disable fault exceptions */
SCB->SHCSR &= ~SCB_SHCSR_MEMFAULTENA_Msk;
/* Disable the MPU and clear the control register*/
MPU->CTRL = 0;
}
HAL_MPU_Enable
此函数用于使能 MPU,一般情况使用参数 MPU_PRIVILEGED_DEFAULT。
void HAL_MPU_Enable(uint32_t MPU_Control)
{
/* Enable the MPU */
MPU->CTRL = MPU_Control | MPU_CTRL_ENABLE_Msk;
/* Enable fault exceptions */
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
/* Ensure MPU setting take effects */
__DSB();
__ISB();
}
此函数支持以下几个参数:
◆ MPU_HFNMI_PRIVDEF_NONE ((uint32_t)0x00000000)
- 此参数设置 MPU 的 CTL 控制寄存器的 PRIVDEFENA 位为 0。
表示禁止了背景区,访问任何未使能 MPU 的区域均会造成内存异常 MemFault。 - 此参数设置 MPU 的 CTL 控制寄存器的 HFNMIENA 位为 0。
表示 NMI 不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会关闭 MPU。
◆ MPU_HARDFAULT_NMI ((uint32_t)0x00000002)
- 此参数设置 MPU 的 CTL 控制寄存器的 PRIVDEFENA 位为 0。
表示使能了背景区,访问任何未使能 MPU 的区域均会造成内存异常 MemFault。 - 此参数设置 MPU 的 CTL 控制寄存器的 HFNMIENA 位为 1。
表示 NMI 不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会保持继续开启 MPU。
◆ MPU_PRIVILEGED_DEFAULT ((uint32_t)0x00000004)
- 此参数设置 MPU 的 CTL 控制寄存器的 PRIVDEFENA 位为 1。
表示使能了背景区,特权级模式可以正常访问任何未使能 MPU 的区域。 - 此参数设置 MPU 的 CTL 控制寄存器的 HFNMIENA 位为 0。
表示 NMI 不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会关闭 MPU。
◆ MPU_HFNMI_PRIVDEF ((uint32_t)0x00000006)
- 此参数设置 MPU 的 CTL 控制寄存器的 PRIVDEFENA 位为 1。
表示禁止了背景区,访问任何未使能 MPU 的区域均会造成内存异常 MemFault。 - 此参数设置 MPU 的 CTL 控制寄存器的 HFNMIENA 位为 1。
表示 NMI 不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会保持继续开启 MPU。
HAL_MPU_ConfigRegion
此函数用于配置 MPU。
void HAL_MPU_ConfigRegion(MPU_Region_InitTypeDef *MPU_Init)
{
/* Check the parameters */
assert_param(IS_MPU_REGION_NUMBER(MPU_Init->Number));
assert_param(IS_MPU_REGION_ENABLE(MPU_Init->Enable));
assert_param(IS_MPU_INSTRUCTION_ACCESS(MPU_Init->DisableExec));
assert_param(IS_MPU_REGION_PERMISSION_ATTRIBUTE(MPU_Init->AccessPermission));
assert_param(IS_MPU_TEX_LEVEL(MPU_Init->TypeExtField));
assert_param(IS_MPU_ACCESS_SHAREABLE(MPU_Init->IsShareable));
assert_param(IS_MPU_ACCESS_CACHEABLE(MPU_Init->IsCacheable));
assert_param(IS_MPU_ACCESS_BUFFERABLE(MPU_Init->IsBufferable));
assert_param(IS_MPU_SUB_REGION_DISABLE(MPU_Init->SubRegionDisable));
assert_param(IS_MPU_REGION_SIZE(MPU_Init->Size));
/* Set the Region number */
MPU->RNR = MPU_Init->Number;
/* Disable the Region */
CLEAR_BIT(MPU->RASR, MPU_RASR_ENABLE_Msk);
/* Apply configuration */
MPU->RBAR = MPU_Init->BaseAddress;
MPU->RASR = ((uint32_t)MPU_Init->DisableExec << MPU_RASR_XN_Pos) |
((uint32_t)MPU_Init->AccessPermission << MPU_RASR_AP_Pos) |
((uint32_t)MPU_Init->TypeExtField << MPU_RASR_TEX_Pos) |
((uint32_t)MPU_Init->IsShareable << MPU_RASR_S_Pos) |
((uint32_t)MPU_Init->IsCacheable << MPU_RASR_C_Pos) |
((uint32_t)MPU_Init->IsBufferable << MPU_RASR_B_Pos) |
((uint32_t)MPU_Init->SubRegionDisable << MPU_RASR_SRD_Pos) |
((uint32_t)MPU_Init->Size << MPU_RASR_SIZE_Pos) |
((uint32_t)MPU_Init->Enable << MPU_RASR_ENABLE_Pos);
}
此函数的形参是一个 MPU_Region_InitTypeDef 类型的结构体变量,定义如下:
typedef struct
{
uint8_t Enable; /*!< Specifies the status of the region.
This parameter can be a value of @ref CORTEX_MPU_Region_Enable */
uint8_t Number; /*!< Specifies the number of the region to protect.
This parameter can be a value of @ref
CORTEX_MPU_Region_Number */
uint32_t BaseAddress; /*!< Specifies the base address of the region to protect. */
uint8_t Size; /*!< Specifies the size of the region to protect.
This parameter can be a value of @ref CORTEX_MPU_Region_Size */
uint8_t SubRegionDisable; /*!< Specifies the number of the subregion protection to disable.
This parameter must be a number between Min_Data = 0x00 and Max_Data = 0xFF */
uint8_t TypeExtField; /*!< Specifies the TEX field level.
This parameter can be a value of @ref CORTEX_MPU_TEX_Levels */
uint8_t AccessPermission; /*!< Specifies the region access permission type.
This parameter can be a value of @ref CORTEX_MPU_Region_Permission_Attributes */
uint8_t DisableExec; /*!< Specifies the instruction access status.
This parameter can be a value of @ref CORTEX_MPU_Instruction_Access */
uint8_t IsShareable; /*!< Specifies the shareability status of the protected region.
This parameter can be a value of @ref CORTEX_MPU_Access_Shareable */
uint8_t IsCacheable; /*!< Specifies the cacheable status of the region protected.
This parameter can be a value of @ref CORTEX_MPU_Access_Cacheable */
uint8_t IsBufferable; /*!< Specifies the bufferable status of the protected region.
This parameter can be a value of @ref CORTEX_MPU_Access_Bufferable */
}MPU_Region_InitTypeDef;
◆ 结构体成员 Number
这个成员是用来设置内存区序号的,用户配置的时候,推荐从 Number0 开始配置,最多到 Number15,
共计 16 个。对应的 HAL 库参数如下:
#define MPU_REGION_NUMBER0 ((uint8_t)0x00)
#define MPU_REGION_NUMBER1 ((uint8_t)0x01)
#define MPU_REGION_NUMBER2 ((uint8_t)0x02)
#define MPU_REGION_NUMBER3 ((uint8_t)0x03)
#define MPU_REGION_NUMBER4 ((uint8_t)0x04)
#define MPU_REGION_NUMBER5 ((uint8_t)0x05)
#define MPU_REGION_NUMBER6 ((uint8_t)0x06)
#define MPU_REGION_NUMBER7 ((uint8_t)0x07)
#if !defined(CORE_CM4)
#define MPU_REGION_NUMBER8 ((uint8_t)0x08)
#define MPU_REGION_NUMBER9 ((uint8_t)0x09)
#define MPU_REGION_NUMBER10 ((uint8_t)0x0A)
#define MPU_REGION_NUMBER11 ((uint8_t)0x0B)
#define MPU_REGION_NUMBER12 ((uint8_t)0x0C)
#define MPU_REGION_NUMBER13 ((uint8_t)0x0D)
#define MPU_REGION_NUMBER14 ((uint8_t)0x0E)
#define MPU_REGION_NUMBER15 ((uint8_t)0x0F)
◆ 结构体成员 BaseAddress
这个结构体成员用来设置内存区的首地址。这个参数跟结构体成员 Size 的配置是比较考究的,一定
要保证首地址跟内存区的大小对齐,比如配置的是 64KB 大小的内存区,那么设置的首地址务必是
64KB 对齐的,也就是这个地址对 64KB,即 0x00010000 求余数等于 0,切记。
使用举例:
/*
*********************************************************************************************************
* 函 数 名: MPU_Config
* 功能说明: 配置 MPU
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* 禁止 MPU */
HAL_MPU_Disable();
/* 配置 AXI SRAM 的 MPU 属性为 Write back, Read allocate,Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置 FMC 扩展 IO 的 MPU 属性为 Device 或者 Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}