小猫爪:嵌入式小知识11-MPU详解及其应用

1 前言

  前段时间被MPU(Memory Protection Unit)搞得头疼欲裂,所以就简单的学习了一下,得空做个总结,接下来就来看看这个MPU到底是个什么玩意?MPU其实是属于ARM核自带的一个外设,是跟核绑定在一起的,所以关于它的资料都在ARM核的各种参考指南中,大家请自行下载参考。

2 MPU简介

  MPU说简单点其实就是一个内存访问权限控制器,如果CPU在访问内存时不符合MPU定义的访问权限的话,那么访问就会被驳回,并且会触发一次错误异常,即Hardfault异常(或者MemManage异常,可通过SCB->SHCSR来配置)。配置内存访问权限的好处主要有:

  1. 避免应用程序破坏其他任务或者OS使用的栈和堆
  2. 避免非特权任务访问对系统可靠性和安全性很重要的外设
  3. 防止恶意代码注入攻击
  4. 控制存储器相关访问属性

3 MPU相关概念

3.1 Memory Map

  众所周知,大部分M核目前是32位寻址,那就代表了核能访问0~2^32-1地址范围,总共4G大小的内存空间。芯片厂商会根据自己的设计将内部Flash,内部SRAM,TCM,外设寄存器,还有外部存储器等等的访问地址映射分布在这4G中,这就被称为Memory Map,所以MPU管理的对象就是整个4G空间。

3.2 MPU Region

  MPU可将整个4G分成若干个区域,然后对每个区域设置地址区间(起始地址和大小)和不同的访问权限来达到预期的保护目标。一般来说,M3核和M4最大支持8个区域,而M7最大支持16个区域,而这个区域就叫做MPU Region。

3.3 Region优先级

  上面提到可对每一个MPU Region设置地址区间,那么如果两个Region有重叠的部分,那么这个时候Region Number大的优先级高。即对于同一块内存区间,Region0设置该区间可访问,而Region3设置该该区间不可访问,那么这个区间的权限遵循Region3的配置为不可访问。基本如下图所示:
在这里插入图片描述

3.4 Background Region

  由于每个区间可设置地址区间,那么就会存在一种情况,那就是等所有的Region都配置完之后,4G空间中还有一些区域没有被所有的Region覆盖到,而这些没有被Region覆盖的区域就叫做Background Region。MPU可单独配置对Background Region的属性为默认特权访问权限还是不可访问。
在这里插入图片描述

3.5 Cache的读写策略

  Cache是M核中的一个高速读写缓存区,在这里对其就不多做介绍了,在这里简单的介绍一下Cache的读写策略,另外Cache的读写策略被MPU控制。

3.5.1 Cache的读操作

  如果CPU要读取的数据在Cache中已经加载好,这就叫读命中(Cache hit),如果Cache里面没有则就叫做读失效(Cache Miss)。如果Cache Miss后,就会有下列两种情况:

  1. 读通(Read through):直接从内存区读取数据,不经过Cache
  2. 读分配(Read allocate):先把数据读取到Cache中,再从Cache中读取数据

3.5.1 Cache的写操作

  如果CPU要写的数据在Cache中已经开辟了对应的区域(专业词汇叫Cache Line,以32字节为单位),这就叫写命中(Cache hit),如果Cache里面没有开辟对应的区域,这就叫写失效(Cache Miss)。

  如果如果Cache hit后,就会有下列两种情况:

  1. 写通(Write-through):在数据更新时,把数据同时写入Cache和存储区操作简单,写入速度慢
  2. 写回(Write-back):只有在数据被替换出Cache时,被修改的缓存数据才会被写到后端存储器,写入速度快,但是一旦Cache中更新的数据未被写入到存储器中去时出现断电,则数据会丢失

  如果如果Cache Miss后,就会有下列两种情况:

  1. 写分配(Write allocate):先把要写的数据载入到Cache中,写Cache,然后再通过flush的方式写入到内存中。
  2. 写不分配(No-write allocate):直接把要写的数据写入到存储器中,不经过Cache。

4 MPU寄存器介绍

  与MPU相关的寄存器也是非常的简单,如下:
在这里插入图片描述

4.1 MPU_TYPE

在这里插入图片描述
  里面记录了MPU的最基础信息,那就是当前的这个MPU所能支持的Region个数(DREGION),一般可以通过读这个寄存器来了解当前片子是否支持MPU。

4.2 MPU_CTRL

在这里插入图片描述
PRIVDEFENA:使能特权模式下的默认存储器映射使能,即Background Region在特权访问模式下的访问权限,1特权访问可正常访问Background Region;0禁止访问,一切访问都会触发错误异常。
HFNMIENA:MPU的规则在异常(NMI, HardFault,MemManage,BusFault等等)中是否起作用。1是,0否。

4.3 MPU_RNR

在这里插入图片描述
  决定当前配置哪一个Region,其中REGION决定Region的序号。在设置每个区域前,写入这个寄存器可以选择要配置的Region。

4.4 MPU_RBAR

在这里插入图片描述
REGION:决定当前配置哪一个Region,其中REGION决定Region的序号。
VALID:决定MPU_RBAR[REGION]是否有效。1则有效;0则无效,这个时候MPU_RNR[REGION]有效。
ADDR:配置起始地址,起始地址必须要是Region大小的整数倍。仔细看ADDR所占的bit为[31:N],这个N是由Region大小决定。举个例子,如果设置Region的大小为64K(0x10000),那么起始地址必须为64K的倍数,N则为16,即[31:16]有效。

4.5 MPU_RASR

在这里插入图片描述
  这是MPU的核心寄存器了,其控制着每个Region的访问属性。

4.5.1 XN

  决定该Region是否可以取指令,类似于Linux中的可执行和不可执行。0可执行,1不可执行。

4.5.2 AP

  决定这块区域针对特权模式和非特权模式的最基本的访问属性,具体如下:
在这里插入图片描述

4.5.3 TEX, C, B, S

  在说TEX, C, B, S之前,先得介绍一下M核访问存储器数据的一个流程如下:
在这里插入图片描述
  在图中可以看到MPU不仅控制核内的访问属性,还控制着核外的访问属性,总之及其复杂,而TEX(类型展开), C(可缓存), B(可缓冲), S(可共享)则就是控制这些和内核外的访问权限。其中S(可共享)决定在多核系统中该Region是否可被其他核访问;对于一般的M核控制器来说,它没有实现系统级缓存,所以对我们来说其实就一个写缓冲(B)需要我们去关心,即上面提到的cache读写策略,但是其它的属性也不能胡乱设置,总之复杂无比。

  系统级缓存又分为inner缓存和outer缓存两级缓存。上面提到一般M核控制器没有实现系统级缓存,那么inner缓存和outer缓存属性一致;如果控制器实现了系统级缓存,这两级缓存是可以被单独控制的,属性可不一致。

  所以在设置TEX, C, B, S这几个属性的时候,需要将这4个属性结合在一起去考虑,具体如下表:
在这里插入图片描述
  仔细看表中最后一项,当TEX最高位=1时,那么这个时候TEX的低两位(AA)决定outer缓存属性,而CB(BB)共同决定inner缓存属性,这适用于那些实现了系统级两级缓存的控制器。AA和BB具体如下:
在这里插入图片描述

4.5.4 SRD

  子Region失能位。MPU将SIZE大于128字节的Region按照大小八等分分成8个子Region,而SRD总共8bit,则依次对应这8个子Region,可以通过SRD分被对这8个Region进行控制。0使能,1失能。

4.5.5 SIZE

  决定了该Region的大小,具体请参考如下代码:

#define ARM_MPU_REGION_SIZE_32B      ((uint8_t)0x04U) ///!< MPU Region Size 32 Bytes
#define ARM_MPU_REGION_SIZE_64B      ((uint8_t)0x05U) ///!< MPU Region Size 64 Bytes
#define ARM_MPU_REGION_SIZE_128B     ((uint8_t)0x06U) ///!< MPU Region Size 128 Bytes
#define ARM_MPU_REGION_SIZE_256B     ((uint8_t)0x07U) ///!< MPU Region Size 256 Bytes
#define ARM_MPU_REGION_SIZE_512B     ((uint8_t)0x08U) ///!< MPU Region Size 512 Bytes
#define ARM_MPU_REGION_SIZE_1KB      ((uint8_t)0x09U) ///!< MPU Region Size 1 KByte
#define ARM_MPU_REGION_SIZE_2KB      ((uint8_t)0x0AU) ///!< MPU Region Size 2 KBytes
#define ARM_MPU_REGION_SIZE_4KB      ((uint8_t)0x0BU) ///!< MPU Region Size 4 KBytes
#define ARM_MPU_REGION_SIZE_8KB      ((uint8_t)0x0CU) ///!< MPU Region Size 8 KBytes
#define ARM_MPU_REGION_SIZE_16KB     ((uint8_t)0x0DU) ///!< MPU Region Size 16 KBytes
#define ARM_MPU_REGION_SIZE_32KB     ((uint8_t)0x0EU) ///!< MPU Region Size 32 KBytes
#define ARM_MPU_REGION_SIZE_64KB     ((uint8_t)0x0FU) ///!< MPU Region Size 64 KBytes
#define ARM_MPU_REGION_SIZE_128KB    ((uint8_t)0x10U) ///!< MPU Region Size 128 KBytes
#define ARM_MPU_REGION_SIZE_256KB    ((uint8_t)0x11U) ///!< MPU Region Size 256 KBytes
#define ARM_MPU_REGION_SIZE_512KB    ((uint8_t)0x12U) ///!< MPU Region Size 512 KBytes
#define ARM_MPU_REGION_SIZE_1MB      ((uint8_t)0x13U) ///!< MPU Region Size 1 MByte
#define ARM_MPU_REGION_SIZE_2MB      ((uint8_t)0x14U) ///!< MPU Region Size 2 MBytes
#define ARM_MPU_REGION_SIZE_4MB      ((uint8_t)0x15U) ///!< MPU Region Size 4 MBytes
#define ARM_MPU_REGION_SIZE_8MB      ((uint8_t)0x16U) ///!< MPU Region Size 8 MBytes
#define ARM_MPU_REGION_SIZE_16MB     ((uint8_t)0x17U) ///!< MPU Region Size 16 MBytes
#define ARM_MPU_REGION_SIZE_32MB     ((uint8_t)0x18U) ///!< MPU Region Size 32 MBytes
#define ARM_MPU_REGION_SIZE_64MB     ((uint8_t)0x19U) ///!< MPU Region Size 64 MBytes
#define ARM_MPU_REGION_SIZE_128MB    ((uint8_t)0x1AU) ///!< MPU Region Size 128 MBytes
#define ARM_MPU_REGION_SIZE_256MB    ((uint8_t)0x1BU) ///!< MPU Region Size 256 MBytes
#define ARM_MPU_REGION_SIZE_512MB    ((uint8_t)0x1CU) ///!< MPU Region Size 512 MBytes
#define ARM_MPU_REGION_SIZE_1GB      ((uint8_t)0x1DU) ///!< MPU Region Size 1 GByte
#define ARM_MPU_REGION_SIZE_2GB      ((uint8_t)0x1EU) ///!< MPU Region Size 2 GBytes
#define ARM_MPU_REGION_SIZE_4GB      ((uint8_t)0x1FU) ///!< MPU Region Size 4 GBytes

5 MPU应用注意事项

  下面列出一些MPU的一些使用注意事项,如下:

  1. Region的起始地址必须是SIZE的整数倍
  2. 下面根据一些常见的存储器属性列出TEX, C, B, S的配置:
    在这里插入图片描述
  3. MPU_CTRL[ENABLE]可使能和失能MPU,正确配置MPU流程为:失能MPU,配置Region属性,使能MPU。
  4. 在配置MPU过程中需要使用存储器屏障指令:DMB(数据存储器屏障),在禁止MPU前使用,确保数据的传输不会重新排序,并且如果有未完成的传输,会等到传输完成之后在写入MPU相应寄存器。DSB(数据同步屏障),在使能MPU后使用,确保接下来的ISB指令只会在写入MPU控制寄存器结束后才执行,可以确保后续的数据传输使用新的MPU设置。ISB(指令同步屏障),用于DSB之后,确保处理器流水线被清空且接下来指令利用更新后的MPU设置被重新读出。
  5. 在实际调试过程中,如果出现问题,可查看SCB寄存器组的CFSR寄存器和MMFAR查看MPU错误标志和触发MPU错误的地址:
    在这里插入图片描述

6 MPU的errata和Workaround

  在ARM官网中,查看相关errata文件,会发现有几条跟MPU相关的errata,就以M7为例,对应文件《Cortex-M7 (AT610) and Cortex-M7 with FPU (AT611) Product Revision r0p1 Software Developers Errata Notice》,每一条errata都标明了有此errata的core版本已经已经在哪一版已被修复。

6.1 1259864

在这里插入图片描述
  这一条的大概意思就是M7在执行cache的写通操作时,如果满足一定条件并满足一定时序则会偶尔发生写通操作时序被打断导致最后数据不正确。该errata暂时没有直接的Workaround,可以通过将该Region的缓冲属性设置成写回,或者将其设置成不可缓冲(no-cacheable)。

6.2 1013783

在这里插入图片描述
  这一条的大概意思就是如果使能了MPU,并使能了PRIVDEFENA位,那么如果执行PLD指令去访问了那些没有被MPU Region覆盖的地址区域的话,可能会导致触发异常(HardFault或者MemManage)。另外M核的Speculative Access可能就会触发一个PLD指令,所以当存在这个bug的时候就会出现一个非常离奇的现象,那就是程序跑着跑着突然就挂了,而且这个挂死的程序位置还在不停的变化。

  这条errata的Workaround也是及其的简单,那就是让MPU的Region覆盖整个4G空间,并且将未被使用的区域的属性设置成no-access。具体做法就是让Region0覆盖整个4G区间,并且配置其AP属性为no-access,再配置其他的Region配置其他已被使用的内存空间,因为Region的优先级特性,后面的Region会覆盖掉Region0的属性。具体操作如下:
在这里插入图片描述

  插入个小经历:之前接触RT1170的时候遇到了一个bug,就是程序跑着跑着程序就挂了,挂了之后,也没有进入异常,调试器连接不上核。后来经过一大堆人的努力,最后就查到了M核的这个errata,而这个errata出现错误是会触发HardFault的,而在RT1170上的现象并不一样。之所以没有触发异常,而是直接挂死,这跟RT1170本身的存储器控制器结构有关,RT1170去访问一个未被使用的一些特定区域时,RT1170是不会触发异常的,而是会把总线直接卡死。所以对这个现象所能给出的推测是:Speculative Access触发了PLD指令,因为被访问目的地址所在Region的访问属性被配置为可访问(比如这个区域处于在Background Region中,或者用户人为将其属性配置成可访问),所以PLD指令就会偶发的去访问该Region,而该Region在RT1170中正好又是未被使用特殊的区域(如未使用外部SDRAM),所以就会导致RT1170内部总线卡死。 (其实至今也没办法证明确实就是这个问题导致的,但是目前只有这一个合理的解释,而且这样修改后,死机问题也没有再次出现了)。解决办法其实跟上述一致,那就是让MPU的Region覆盖整个4G空间,并且将未被使用的区域的属性设置成no-access,具体操作方法同上所述。下面贴出RT1170的errata截图,上面描述了具体的描述和Workaround,感兴趣的人可以自行查阅。
在这里插入图片描述

6.3 834922

在这里插入图片描述
  这一条的大概意思就是当HFNMIENA没有被使能时,即MPU的规则在异常中不生效,而且还要使用MPU的默认的Memory Map配置,这个时候去设置FAULTMASK的时候如果被打断了,那么接下来就会导致不可预估的事情发生(其实这个bug大概率是遇不到了,不仅产生条件苛刻,而且在r0p2版本早就修复了,不用太过担心)。

  这条errata的Workaround也是及其的简单,就是在置位FAULTMASK之前,先把PRIMASK置位,具体操作如下:
在这里插入图片描述

6.4 838169

在这里插入图片描述
  这一条errata其实是cache的bug,跟MPU本身并没有多大的关系,只是说在配置MPU和cache存储之间如果没有DSB屏障指令的话可能会间接导致这个bug的发生。所以在这里再次强调,在配置MPU的时候,一定要按照5 MPU应用注意事项中的第四条的要求插入存储器屏障指令。

6.5 834923

在这里插入图片描述
这一条的大概意思就是有一种情况可能会导致MPU工作不正常,由于导致这种情况出现的条件非常苛刻,而且也没有workaround去避免,所以只能做的就是尽量去避免这种情况发生,在这里就不多作解释和介绍了,具体的条件如下:
在这里插入图片描述

END

  • 21
    点赞
  • 120
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小猫爪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值