?一、目的
????????MPU即Memory Protection Unit(内存保护单元)更准确的讲应该叫做存储器保护单元,因为此处的Memory不单单指SRAM,也包括ROM、FLASH、FMC等等,是地址空间的概念。
????????其实MPU在Cortex-M0/M3/M4/M33系列的STM32 MCU(F1/F4系列)就有,只是M0/M3/M4系列的MCU主频都偏低没有Cache功能并且我们一般也很少关心访问权限和存储器类型问题(MCU复位后默认工作在特权级线程模式),所以基本上大家都不会去用它(默认的内存映射和属性配置足够使用);但是当我们在使用Cortex-M7内核的MCU时(例如STM32H750),不好好的去学习一下MPU并进一步去理解Cache的相关知识,那么就可能会遇到奇奇怪怪的问题却无法定位解决。
????????我就是在使用Quad SPI DMA传输时遇到DMA读取然后CPU进行比较时发现数据不一致问题后纠结了好久(以为是自己哪边没有配置好导致的),并且也在网上找了很多别人所讲的各种教程,没有一个能讲清楚的,后来才知道是因为默认的Cache配置导致的问题(如果使能数据Cache的情形下不修改默认的Cache策略)。
? ? ? ? 本篇的主要目的是介绍MPU到底能做些什么,为什么需要它,以及如何使用它;
????????下一篇中讲解Cache是什么,MPU如何和Cache配合提升系统性能以及使用注意点。
? ? ? ? 由于MPU涉及到的内容比较多,需要大家有一定的Cortex-M系列的知识,所以大家第一遍阅读的时候如果看不懂就多看几遍,英文好的直接看下面提供的链接(这样学到的就是别人学不到的)
????????先放几张图方便后面讲解
????????上图是STM32各个系列的MPU配置情况对比情况,我们可以看到每个Cortex-M内核最大可以有多少个Region,每个Region的大小设置范围,Region memory attributes是重点;需要注意的是M33区别较大,请自行查阅资料理解,其他相关内容我们下面都会一一讲解。
????????
????????上图是MPU里面涉及的Region的重叠以及优先级概念(只要记住数字越大优先级越高)。
? ? ? ? 上图是M7的内存地址映射,大家肯定再熟悉不过了,当时我们可能只关注了各个区的地址分布、大小以及基本类型,但是内存地址映射中还有一个很重要的内容叫做Memory Types and Attributes(内存/存储器类型和属性);之所以重要是因为内存/存储器类型和属性决定了访问这块区域的行为。
????????
? ? ? ? ?上图是使用Cache时可能存在的数据一致性问题的一个经典示例。这类型情形一般发生在多主机访问时,例如CPU和DMA。
? ? ? ? 上面的几张示例图下面都会有详细的讲解,下面我们将开始正式的MPU知识学习之旅。
二、介绍
????????按照惯例我们先贴上官网提供的资料(有ARM官网的,有NXP的,也有ST的)
ARM Cortex-M7 Devices Generic User Guide r1p1https://developer.arm.com/documentation/dui0646/b/Cortex-M7-Peripherals/Optional-Memory-Protection-Unit? ? ? ? 这个资料是ARM官网的关于M7架构的最权威资料,如果想对系统架构深入了解,这个是必须要看的,也是提升自己的最有效途径。
Managing memory protection unit in STM32 MCUs - Application notehttps://www.st.com/resource/en/application_note/dm00272912-managing-memory-protection-unit-in-stm32-mcus-stmicroelectronics.pdf#:~:text=The%20cache%20control%20is%20done%20globally%20by%20the,that%20provides%20up%20to%204%20Gbytes%20of%20addressablememory.Level 1 cache on STM32F7 Series and STM32H7 Serieshttps://www.st.com/resource/en/application_note/dm00272913-level-1-cache-on-stm32f7-series-and-stm32h7-series-stmicroelectronics.pdf STM32F7 Series and STM32H7 Series Cortex?-M7 processor programming manualhttps://www.st.com/content/ccc/resource/technical/document/programming_manual/group0/78/47/33/dd/30/37/4c/66/DM00237416/files/DM00237416.pdf/jcr:content/translations/en.DM00237416.pdf
? ? ? ? 本文的知识点都是从上面的几个官方文档整理过来并加上自己的一些理解(如果有不对的地方,请以官网为准或留言告知)。这边也建议参考安富莱、原子哥等等资料作为参照对比。 【STM32H7教程】第24章 STM32H7的Cache解读(非常重要)_Simon223的博客-CSDN博客_stm32的cachehttps://blog.csdn.net/Simon223/article/details/91497513? ? ? ? ?其实我也是看了安富莱的资料后又去看了官网的英文资料,也看了好几遍,才想着写下自己的理解并作为学习笔记,希望对你有用(点赞关注收藏三连击)。
三、实战?
? ? ? ? 1、为什么需要MPU?
????????
? ? ? ? MPU可以让嵌入式系统更加鲁棒性和安全性:
- 禁止用户程序破坏被关键任务使用的数据(例如OS内核代码使用的数据)
- 定义SRAM内存区域禁止执行代码以防止代码注入攻击
- 改变内存/存储器访问属性以及访问权限(说白了就是你可以控制某些地址区间按照自己的需求进行访问)? ? ? ? ? ? ? ??
? ? ? ? 2、MPU特性
?????????
? ? ? ? ?MPU将Memory Map(内存映射)分成多个Regions(区),并且定义每个区的首地址、大小、访问权限和存储器属性:
- 每个区域独立的attribute(属性)设置
- 允许区域重叠,并且具有优先级(数字越大优先级越高)
- 将存储器属性导出给系统
? ? ? ? 3、什么是Memory Map?
? ? ? ? 上图就是Memory Map,博文开头就贴过类似的图;也就是说在Cortex-M系列的处理器中将 32位地址空间分成了大小不同的区域,每个区域有各自的功能以及访问权限。
? ? ? ? 什么是访问权限?
????????
? ? ? ? RW 可读可写
? ? ? ? RO 只读
? ? ? ? WO 只写
? ? ? ? Privileged 特权级才能访问
? ? ? ? Unprivileged 非特权级(用户级)即特权级和非特权级都可以访问?
? ? ? ? 4、MPU描述
?????????
? ? ? ? 基本上M7内核的MCU芯片最多可以配置16个内存区域,并且每个区域又可以分成8个子区域(区域大小至少要256字节),每个子区域大小总是相同的,最小的子区域大小为32字节(Cache? Line的大小为32字节),并且每个子区域也是可以独立使能的。
? ? ? ? 每个区都是有一个编号(0-15),并且还有一个默认的区(其编号为-1),也叫做背景区,编号越大优先级越高。区之间可以重叠也可以嵌套;如果区间有重叠,那么重叠部分的访问属性由编号大的区来决定。
????????
? ? ? ? 上图中Region3被Region5重叠的部分访问权限、内存属性由Region5决定。
? ? ? ? MPU是统一的,也就是说数据(data)与代码(code)都是由同一个MPU来管理,MPU都统一对待,通过Region配置来标识这个区域是code还是data。Cache的控制是由Cache自身的控制寄存器管理的,但是MPU可以指定这块区域的cache策略。
? ? ? ? 存储器类型
?????????
?????????内存映射配合MPU的编程设置我们可以将内存映射划分成各个区,并且每个区都有自己的存储器类型以及访问属性,就是这些类型和访问属性决定了每个区各自的访问权限和行为。
? ? ? ? 有三种类型的存储器类型,分别是
- Normal CPU会以更高效的方式加载(LDR)存储(STR)字节、半字、字,CPU对这种类型存储器的加载存储不一定会完全按照程序代码的顺序执行
- Device 加载和存储严格按照次序进行,这样可以保证寄存器可以按照正确地顺序设置,故Peripheral区默认就是Device类型
- Strongly ordered 跟Device类似,但是CPU会等待当前加载存储执行完毕后才执行下一条指令,但会导致性能下降
? ? ? ? 外部存储系统可以缓存(buffer)对Device类型的存储器的写操作,而Strongly-Ordered类型的存储器则要求一个执行完毕再执行下一个(会有一个等待过程),所以更加严格。
? ? ? ? 下面我特意做了一张图帮助大家理解,更多的知识大家可以从上面的官方文档中去学习了解。
? ? ? ??
? ? ? ? 共享属性
?????????
????????这张图大家应该不陌生,如果这张图能够理解清楚,那基本上可以算得上是H7的开发高手了,这张图展示了整个H7的各个总线间、总线与CPU间的关系,我们可以看到每个总线上都有多个Master以及Slave,同一个Slave可以多个Master进行访问(甚至可以同时访问),共享属性就是Master间的关系。
????????存储器属性
????????
? ? ? ? MPU_RASR寄存器控制存储器属性,包括:?
????????XN控制代码执行,如果某个区可以执行指令,那么XN必须为0;
? ? ? ? AP则是用于设置访问权限,如下表
? ? ? ? ?访问权限包括特权级和非特权级(用户级),上表则详细的给出了对应的读写权限。
? ? ? ? S则代表了是否可以共享,内存系统提供了总线多主机间的数据同步,例如CPU和DMA控制器。Stronly-ordered类型的存储器总是共享的;
????????如果多个主机可以访问一个非共享的区域,那么软件层面就必须确保数据的同步以及一致性;STM32F7/H7系列不支持硬件层面的数据一致性。
????????如果某个区是shareable属性那就代表non-cacheable。?
? ? ? ? TEX/C/B用于定义某个区的cache属性的,这个我们在下一篇中讲解,本篇只将图表粘贴出来
?
? ? ? ? ?关于Write-Back/Write Allocate/Write-through/Read Allocate这些术语都在下一篇中介绍,只要记住这些都是跟Cache策略有关即可;另外可以看到Strongly-Ordered类型的访问总是Shareable的,Device类型的要看具体配置。
? ? ? ? 关于上图中TEX值是0b1BB那一组设置需要特别说明一下,因为STM32F7/H7系列中只实现了L1 Cache,所以只有inner policy;这一点可以在STM32 Pack包中的宏定义也可以看到。
? ? ? ? ?上图中关于TEX只有0、1、2三个数值(其他文档中只是说一般用不上,但是不会告诉你原因,果断关注+收藏+点赞);
? ? ? ? 关于inner/outer policy的区别我自己研究明白了再来补充(L1/L2 cache的区别)。
? ? ? ? M7系列TCM内存总是non-cacheable/non-shared normal类型的内存(MPU配置不对这块区间起作用),其他系列受控于MPU管理。?
? ? ? ? 5、MPU寄存器描述?
? ? ? ? 下面我们讲解一下MPU的寄存器相关的一些内容
? ? ? ? 上图是MPU寄存器组,都是特权级访问
? ? ? ? ? ? ? ? MPU_TYPE寄存器指明MPU的一些特征,如是否有MPU外设,支持多少个Regions等。?
????????????????IREGION指明支持的MPU支持的指令Region个数,总是为0,因为我们的MPU是unified(统一的);
? ? ? ? ? ? ? ? DREGION指明支持的MPU支持的数据Region的个数,一般是8个或者16个;
? ? ? ? ? ? ? ? SEPARATE指明指令和数据映射是否是统一的,那肯定是统一的;
?????????
? ? ? ? ENABLE用于使能MPU
? ? ? ? HFNMIENA用于控制是否在hard fault、NMI、FAULTMASK异常处理时禁用MPU
? ? ? ? PRIVDEFENA用于当MPU使能时,是否使能默认的内存映射作为背景区;需要注意的是,如果我们在配置MPU Region只配置了需要用的那些地址区间,那么其他未配置的区间就不能够被访问,如果访问就会触发MemMange异常;所以一般情况下我们都会打开背景区。(背景区使用默认的内存映射,并且只能特权访问)。
? ? ? ? ?
????????上图就是默认的存储器访问行为,每个地址空间的首地址、大小、区类型、存储器类型。
? ? ? ? ? ? ? ? ?
????????上图就是默认的内存映射对cache和sahreable的一些默认配置。?
? ? ? ? 6、MPU API接口描述
????????
#if (__MPU_PRESENT == 1)
void HAL_MPU_Enable(uint32_t MPU_Control);
void HAL_MPU_Disable(void);
void HAL_MPU_ConfigRegion(MPU_Region_InitTypeDef *MPU_Init);
#endif /* __MPU_PRESENT */
? ? ? ? ?MPU总共有三个接口用于管理MPU:
/**
* @brief Disables the MPU
* @retval None
*/
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;
}
/**
* @brief Enables the MPU
* @param MPU_Control Specifies the control mode of the MPU during hard fault,
* NMI, FAULTMASK and privileged access to the default memory
* This parameter can be one of the following values:
* @arg MPU_HFNMI_PRIVDEF_NONE
* @arg MPU_HARDFAULT_NMI
* @arg MPU_PRIVILEGED_DEFAULT
* @arg MPU_HFNMI_PRIVDEF
* @retval None
*/
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,配置MPU之前必须先禁止MPU;
? ? ? ? 关于__DSB()和__ISB()这两个函数,后面有时间也会分享一下,都是一些高级知识。
? ? ? ? MPU_Control可以设置的值为
#define MPU_HFNMI_PRIVDEF_NONE ((uint32_t)0x00000000)
#define MPU_HARDFAULT_NMI ((uint32_t)0x00000002)
#define MPU_PRIVILEGED_DEFAULT ((uint32_t)0x00000004)
#define MPU_HFNMI_PRIVDEF ((uint32_t)0x00000006)
?????????Table 86已经详细讲述了这些值的含义,请翻看上面的描述;一般情况下都选择MPU_PRIVILEGED_DEFAULT这个值。
/**
* @brief Initializes and configures the Region and the memory to be protected.
* @param MPU_Init Pointer to a MPU_Region_InitTypeDef structure that contains
* the initialization and configuration information.
* @retval None
*/
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));
/* Set the Region number */
MPU->RNR = MPU_Init->Number;
if ((MPU_Init->Enable) != 0UL)
{
/* Check the parameters */
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));
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);
}
else
{
MPU->RBAR = 0x00;
MPU->RASR = 0x00;
}
}
????????HAL_MPU_ConfigRegion用于配置MPU,需要配置哪个Region时就调用一次。
? ? ? ? 注意一下MPU_Region_InitTypeDef结构体的各个字段的含义
/** @defgroup CORTEX_MPU_Region_Initialization_Structure_definition MPU Region Initialization Structure Definition
* @brief MPU Region initialization structure
* @{
*/
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;
? ? ? ? ?需要说明的是Size字段只能是这些值
#define IS_MPU_REGION_SIZE(SIZE) (((SIZE) == MPU_REGION_SIZE_32B) || \
((SIZE) == MPU_REGION_SIZE_64B) || \
((SIZE) == MPU_REGION_SIZE_128B) || \
((SIZE) == MPU_REGION_SIZE_256B) || \
((SIZE) == MPU_REGION_SIZE_512B) || \
((SIZE) == MPU_REGION_SIZE_1KB) || \
((SIZE) == MPU_REGION_SIZE_2KB) || \
((SIZE) == MPU_REGION_SIZE_4KB) || \
((SIZE) == MPU_REGION_SIZE_8KB) || \
((SIZE) == MPU_REGION_SIZE_16KB) || \
((SIZE) == MPU_REGION_SIZE_32KB) || \
((SIZE) == MPU_REGION_SIZE_64KB) || \
((SIZE) == MPU_REGION_SIZE_128KB) || \
((SIZE) == MPU_REGION_SIZE_256KB) || \
((SIZE) == MPU_REGION_SIZE_512KB) || \
((SIZE) == MPU_REGION_SIZE_1MB) || \
((SIZE) == MPU_REGION_SIZE_2MB) || \
((SIZE) == MPU_REGION_SIZE_4MB) || \
((SIZE) == MPU_REGION_SIZE_8MB) || \
((SIZE) == MPU_REGION_SIZE_16MB) || \
((SIZE) == MPU_REGION_SIZE_32MB) || \
((SIZE) == MPU_REGION_SIZE_64MB) || \
((SIZE) == MPU_REGION_SIZE_128MB) || \
((SIZE) == MPU_REGION_SIZE_256MB) || \
((SIZE) == MPU_REGION_SIZE_512MB) || \
((SIZE) == MPU_REGION_SIZE_1GB) || \
((SIZE) == MPU_REGION_SIZE_2GB) || \
((SIZE) == MPU_REGION_SIZE_4GB))
? ? ? ? 并且需要注意的是BaseAddress值要能够被Size值整除。例如Size选择为MPU_REGION_SIZE_4KB,那么BaseAddress就必须能够被4KB整除。
????????
针对MPU接口的使用,官方文档有这样一个例子
void MPU_RegionConfig(void)
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* Disable MPU */
HAL_MPU_Disable();
/* Configure RAM region as Region N°0, 8kB of size and R/W region */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x20000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_8KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Configure FLASH region as REGION N°1, 1MB of size and R/W region */
MPU_InitStruct.BaseAddress = 0x08000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_1MB;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Configure FMC region as REGION N°2, 0.5GB of size, R/W region */
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512MB;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Enable MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
????????以上就是MPU的基本知识点,关于Cache的使用和配置会在后面的博文中讲解(点赞+关注收藏不迷路)。
? ? ? ? ? ? ? ??????????
?????????????????
|