ARM学习(42)CortexM3/M4 MPU配置
笔者之前学习过CortexR5的MPU配置,现在学习一下CortexM3/M4 MPU配置
1、背景介绍
笔者在工作中遇到NXP MPU在访问异常地址时,就会出现总线挂死,所以需要MPU抓住异常,就需要配置MPU。具体背景情况可以参考ARM学习(41)NXP MCU总线挂死,CPU could not be halted以及无法连接Jink。笔者之前还研究了Cortex-R5的MPU配置。
比如经常出现如下场景:
NXP的MCU使用的是LPC64016系列,是有配置MPU的。功能特点如下:
然后笔者又搜索了一下ST的芯片,主要针对F1和F4系列,F1大部分MCU没有配置MPU,F4基本都配置了MPU。具体得可以去下载ST的MCU手册,ST官网MCU系列,STMCU文档下载地址。
STM32F1系列只有STM32F10xxF、10xxG的才有MPU,比如101xF、103xF或者105xF系列,这些系列有个共同的特点就是Flash大,从768K到1MB。
STM32F4基本都配置MPU,其属于中高端产品。
STM32的产品 性能以及内核架构如下图所示。
2、MPU配置介绍
CortexM3、M4的MPU配置主要是几个寄存器的配置,可以配置MPU的region、访问权限和地址大小等属性。
寄存器主要有如下几个:
寄存器名字 | 访问权限 | 地址 | 说明 |
---|---|---|---|
MPU_Type | RO(只读) | 0xE000ED90 | 说明MPU的region个数,默认是8,不可修改 |
MPU_CTRL | RW(读写) | 0xE000ED94 | 主要是使能MPU |
MPU_RNR | RW(读写) | 0xE000ED98 | 选择使能的MPU region,可选 |
MPU_RBAR | RW(读写) | 0xE000ED9C | MPU 属性以及size配置 |
MPU_RASR | RW(读写) | 0xE000EDA0 | MPU保护的基地址配置 |
接下来具体看一下其配置信息。
2.1 MPU Type
只读寄存器,中间8位有效,可以读出数据来。
实际测试如下:
printf("mpu region=%d\r\n",(MPU->TYPE >>8)&0xff);
2.2 MPU CTRL
可读可写寄存器,
- 背景region的作用,如果不打开,默认都不可访问,可以少一个region
- NMI以及hardfault中断对mpu的操作
- 使能MPU的操作
笔者来测试一下BIT2,不打开背景region,然后把region 0的4G区域关闭,然后使能MPU,代码如下:
/* Disable MPU */
ARM_MPU_Disable();
/**省略保护区域配置**/
/* Enable MPU */
ARM_MPU_Enable(0);
异常代码还一样
int* test_data_ptr = ( int*)0xF0000000;
*test_data_ptr = 0x1234;
printf("*test_data_ptr=%d\r\n",*test_data_ptr);
测试结果如下:可以看到异常已经抓到,数据地址是0xF0000000,代码地址10000348A,可以在trace32上面看到,说明背景region起作用了,保护了非法地址。
再来看一下,打开region,使得可以全部可以访问,但是异常地址没有保护,代码如下:
/* Disable MPU */
ARM_MPU_Disable();
/**省略保护区域配置**/
/* Enable MPU */
ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_HFNMIENA_Msk);
测试结果如下,总线直接挂死,然后没有任何反应。
结论:
- 如果是裸机系统,该BIT位可以作为一个背景region,其他region可以保护更多的区域,相当于增加了一个全局region的处理。
- 如果有操作系统的情况下,可以限制用户对特殊代码的访问。
2.3 MPU RNR
选择对应的region,进行属性和地址配置,往往不用这个寄存器,通过其他方式处理。
2.4 MPU RASR
MPU属性配置寄存器,配置权限访问,以及cacheable,共享等属性。
主要是两个属性:
- 一个是访问属性,如上图所示
- 另外一个是cache等属性,如下表所示
TEX | C | B | S | 存储器类型 | 描述说明 |
---|---|---|---|---|---|
000 | 0 | 0 | 1 | 严格顺序 | 严格顺序,总是共享 |
000 | 0 | 1 | 1 | 设备 | 共享的设备 |
000 | 1 | 0 | 0/1 | 普通 | 片外或者片内写通cache,不写分配 |
000 | 1 | 1 | 0/1 | 普通 | 片外或者片内写回cache,不写分配 |
001 | 0 | 0 | 0/1 | 普通 | None-Cacheable |
001 | 0 | 1 | 0/1 | N/A | N/A |
001 | 1 | 0 | 0/1 | N/A | N/A |
001 | 1 | 1 | 0/1 | 普通 | 片外或者片内写回cache,读写分配 |
010 | 1 | x | 0/1 | 设备 | 不共享的设备 |
010 | 0 | 1 | 0/1 | N/A | N/A |
010 | 1 | x | 0/1 | N/A | N/A |
1BB | A | A | 0/1 | N/A | Cache Memory,BB是片外,AA是片内 |
AA and BB | Cache类型 |
---|---|
00 | None-Cachelable(没有cache) |
01 | write back,write and read allocate(写回,读写分配cache) |
10 | write through,no write allocate(写通,不写分配) |
11 | write back,no write allocate(写回,不写分配) |
再来看一下一般的设备如何配置
Type | memory Type | 配置信息 |
---|---|---|
ROM/Flash/ | Normal memory | TEX=0,C=1,B=0,S=0,没有share,写通cache |
01 | Normal memory | TEX=0,C=1,B=0,S=1,共享,写通cache |
10 | Normal memory | TEX=0,C=1,B=1,S=1,共享,写回cache |
11 | Device | TEX=0,C=0,B=1,S=1,共享Devices |
2.5 MPU RBAR
可以通过region字段覆盖 RNR寄存器的region配置,所以减少一个寄存器的操作。
- 基地址要以容量的大小进行对齐(即基地址可以整除容量),这样设计会让硬件设计简单。
- 容量的大小最低从32开始,都是2的次幂,用编码表示其大小。 容量=1<< (容量编码+1)
#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
3、实际例子测试
笔者来看一下MPU的配置代码。
region号 | 基地址 | 长度 | 属性 |
---|---|---|---|
0 | 0x00000000 | 4G | 不可访问,背景region,TEX=0,S=0,C=1,B=0,不共享,写通cache |
1 | 0x00000000 | 256KB | 可访问,TEX=0,S=0,C=1,B=0,不共享,片外或者片内写通cache,不写分配,普通memory |
2 | 0x03000000 | 64KB | 可访问,TEX=0,S=0,C=1,B=0,不共享,片外或者片内写通cache,不写分配,普通memory |
3 | 0x10000000 | 8MB | 只读,TEX=0,S=0,C=1,B=0,不共享,片外或者片内写通cache,不写分配,普通memory |
4 | 0x20000000 | 128KB | 可访问,TEX=0,S=0,C=1,B=0,不共享,片外或者片内写通cache,不写分配,普通memory |
5 | 0x40000000 | 256MB | 可访问,TEX=0,S=1,C=0,B=1,共享Devices,没有cahce |
6 | 0xE0000000 | 1MB | 可访问,EX=0,S=1,C=0,B=1,共享Devices,没有cahce |
void mpu_config()
{
/* Disable MPU */
ARM_MPU_Disable();
/* Region 0 setting: Memory with no access type, not shareable, write trough */
MPU->RBAR = ARM_MPU_RBAR(0, 0x00000000);
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_NONE, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_4GB);
/* Region 1 setting: Memory with full access type, not shareable, write trough */
MPU->RBAR = ARM_MPU_RBAR(1, 0x00000000);
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_256KB);
/* Region 2 setting: Memory with full access type, shareable, none-cacheable */
MPU->RBAR = ARM_MPU_RBAR(2, 0x03000000);
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_64KB);
/* Region 3 setting: Memory with read-only access type, not shareable, write trough */
MPU->RBAR = ARM_MPU_RBAR(3, 0x10000000);
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_RO, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_8MB);
/* Region 4 setting: Memory with full access type, not shareable, write trough */
MPU->RBAR = ARM_MPU_RBAR(4, 0x20000000);
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_128KB);
/* Region 5 setting: Memory with full access type, shareable, none-cacheable */
MPU->RBAR = ARM_MPU_RBAR(5, 0x40000000);
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 1, 0, 1, 0, ARM_MPU_REGION_SIZE_256MB);
/* Region 6 setting: Memory with full access type, shareable, none-cacheable */
MPU->RBAR = ARM_MPU_RBAR(6, 0xE0000000);
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 1, 0, 1, 0, ARM_MPU_REGION_SIZE_1MB);
/* Enable MPU */
ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_HFNMIENA_Msk);
}
笔者是基于这款芯片的memory创立的,然后根据芯片的地址等情形,可以确认相关memory的MPU配置属性。
然后笔者还碰到一个问题,之前不了解FW会调用0x03000000 ROM Code 的函数,所以这个区域没有配置,结果就访问异常了。错误类型也是指令访问异常。
笔者尝试配置rom code为只读测试一下,之前配置的时候,可读可写。
结论:配置这样也可以访问。
/* Region 2 setting: Memory with full access type, shareable, none-cacheable */
MPU->RBAR = ARM_MPU_RBAR(2, 0x03000000);
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_64KB);
2.6 subregion
再介绍一下subregion,就是一个region区域比较大,会被等分成8份,每份区域可以由一个bit去禁止。
所以可以配置更大的region,然后通过subregion再去细分。
这样笔者再新增一个region,然后禁止某些subregion,来做一些测试。
如下图代码所示,笔者打开了一个异常区域,0xF0000000,但是该区域的前128KB禁止,意味着其受背景region保护,访问进异常,但是其他区域可以访问,会导致总线挂死。
/* Region 7 setting: Memory with full access type, shareable, none-cacheable */
MPU->RBAR = ARM_MPU_RBAR(7, 0xF0000000);
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 1, 0, 1, 1, ARM_MPU_REGION_SIZE_1MB);
如下图所示,该区域访问被抓住现场,地址0x100034AE,数据地址:0xF0000000。
如果全部区域都访问,则总线挂死,无法调试。
/* Region 6 setting: Memory with full access type, shareable, none-cacheable */
MPU->RBAR = ARM_MPU_RBAR(7, 0xF0000000);
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 1, 0, 1, 0, ARM_MPU_REGION_SIZE_1MB);
4、参考
1、STM32F1-F4参考手册以及下载
2、Cortex M3/CM4权威指南
3、Cortex M3权威指南中文版