1 MM32F5330 MPU简介
灵动微电子发布了搭载安谋科技“星辰”STAR-MC1处理器的全新高性能 MM32F5 微控制器系列,该系列在内核、总线和外设配置等多个方面进行了创新,内核上更是首次搭载了 Armv8-M 架构的 “星辰” STAR-MC1 处理器。Armv8-M 架构相较于 Armv7-M 架构,除了性能显著提升以外,其中一项就是更加安全:Armv8-M 架构引入了 TrustZone 技术,并强化了内存保护单元(MPU),让代码运行在更安全的环境中。
MPU 在 4GB 地址映射中定义保护区域。Armv8-M 上的MPU 有8个region,每一个region都有起始地址,结束地址,访问权限和内存属性,每一个region都有单独的属性。和以往Armv7-M 的MPU有所不同,Armv8-M的MPU不支持region overlap,如果一个地址同时出现在两个不同的region中,会导致HardFault。如果程序访问被MPU禁止的内存位置,处理器就会生成一个 MemManage异常。
MPU 本质上就是为了保护某一段地址区域不被非授权状态的程序进行访问。通常嵌入式操作系统使用MPU进行内存保护,内核可以根据进程动态更新MPU区域设置。MPU 可以让嵌入式系统更加健壮,以及保护一些加密区域。MPU 具有以下能力可以增加系统的健壮性:
可以阻止用户去破坏操作系统需要使用的数据
可以防止一个任务去非法访问其他任务的数据,将任务完全隔离开
可以把关键数据区设为只读,从而不被破坏
检测其他意外访问,比如堆栈溢出,数组越界等
2 内存类型
Armv8中将内存分为两种类型:Normal memory和Device memory。Normal memory适用于系统中的大部分内存,而Device memory则适用于外设所使用的内存。
Normal memory,主要指RAM,ROM,FLASH等,处理器以及编译器都可以对程序做优化,处理器还可以增加repeate,reorder,merge的操作。在强制访问顺序的情况下,需要调用内存屏障指令。
Device memory,通常都是外设对应的内存映射。Device类型用于可能有副作用的位置,不可缓存,不允许对标记为Device的区域进行推测性数据访问。
Device memory属性:
1) G:Gather,多个内存访问可以合并
2) R:Reordering,对内存访问指令进行重排
3) E:Early Write Ack,写操作的Ack可提前应答
四种Device memory:
1) Device nGnRnE,不允许gather、reorder、early
2) Device nGnRE,允许early
3) Device nGRE,允许reorder、early
4) Device GRE,允许gather、reorder、early
下表显示了可能的MPU region属性,包括Shareable和Cacheable属性。
Armv8内存类型和属性还有很多细节,对MPU region属性配置会涉及到Cache读写策略的内容,感兴趣的同学可以先查阅相关资料,进行详细了解。本章节我们着重理解MPU的功能和作用,并进行简单验证,关于Cache内容在后续章节中再进行说明。
3 MPU寄存器模组
3.1 MPU主要有以下寄存器
3.2 MPU_TYPE
MPU_TYPE寄存器用来表示MPU是否存在以及它支持多少个region。
3.3 MPU_CTRL
MPU_CTRL用来使能MPU、使能backgroup map、使能NMI中MPU是否有效。
3.4 MPU_RNR
MPU_RNR用来选择region,在访问MPU_RBAR和MPU_RLAR之前,必须先写入MPU_RNR来选择region。
3.5 MPU_RBAR
MPU_RBAR定义了region的起始地址。
3.6 MPU_RLAR
MPU_RLAR定义了region的上限地址以及region属性选择。
3.7 MPU_MAIR0和MPU_MAIR1
MPU_MAIR0和MPU_MAIR1提供与AttrIndex值对应的内存属性编码。
每一个region属性MARI_ATTR占8位。
如果MAIR_ATTR[7:4]为0,那么MAIR_ATTR定义如下:
如果MAIR_ATTR[7:4]不为0,那么MAIR_ATTR定义如下:
4 MPU配置
关于MPU的配置可以参考灵动微电子官网的LibSamples,具体在core_starmc1.h和mpu_armv8.h文件定义了MPU寄存器映射及接口函数,下表中列出部分函数:
5 MPU测试
5.1 region read/write测试
定义一个指针变量指向地址0x20006000位置,在MPU关闭时,该地址可以正常进行读写,通过配置MPU将0x20006000 - 0x20006FFF区域设置为region0只读,使能MPU后再进行写访问,仿真观测运行情况。测试代码如下:
void mpu_readwrite(void)
{
volatile uint32_t *temptr = (volatile uint32_t *)0x20006000UL;
MPU_Cmd(MPU, DISABLE); //Disable MPU
*temptr = 0x00;
printf("%x : %x\n", (uint32_t)temptr,*temptr);
*temptr = 0xA5;
printf("%x : %x\n", (uint32_t)temptr,*temptr);
//Enable MPU region0:0x20006000 - 0x20006FFF
//Read-only,Execution not permitted,Device memory
MPU_SelectRegion(MPU, 0);
MPU_SetRegionBase(MPU, 0x20006000UL, REGION_NON_SHAREABLE, REGION_RO_ANY, REGION_XN);
MPU_SetRegionLimit(MPU, 0x20006FFFUL, 0, REGION_EN);
MPU_SetRegionAttr(MPU, 0, 0);
MPU_HfnmienaCmd(MPU, ENABLE);
MPU_PrivdefenaCmd(MPU, ENABLE);
MPU_Cmd(MPU, ENABLE);
printf("Test start:\n");
//After MPU is enabled, check whether the write permission is granted.
printf("%x : %x\n", (uint32_t)temptr,*temptr);
//The hardfault will be triggered when the code is executed here.
*temptr = 0x5A;
printf("%x : %x\n", (uint32_t)temptr,*temptr);
printf("Test over!\n");
}
仿真查看MPU配置和代码执行预期一致:
串口调试助手打印情况:
打印Test start之前的数据可以进行读写,使能MPU之后先打印一次数据,即可读,但是运行到赋值语句 * temptr = 0x5A,就进入了HardFault,说明该地址不可写。
修改测试代码设置region0为可读写:
MPU_SelectRegion(MPU, 0); MPU_SetRegionBase(MPU, 0x20006000UL, REGION_NON_SHAREABLE, REGION_RW_ANY, REGION_XN); MPU_SetRegionLimit(MPU, 0x20006FFFUL, 0, REGION_EN); MPU_SetRegionAttr(MPU, 0, 0);
仿真查看MPU配置和代码执行预期一致:
串口调试助手打印情况:
测试代码得到全部执行,使能MPU之后先打印一次数据,即可读,运行赋值语句* temptr = 0x5A后,打印该地址的数据是0x5A,说明写正常。
综上,MPU可以有效设置区域的读写权限。
5.2 region overlap测试
定义一个指针变量指向地址0x20006000位置,在MPU关闭时,该地址可以正常进行读写,通过配置MPU将0x20006000 - 0x20006FFF区域设置为region0可读写,将0x20005000 - 0x20007FFF区域设置为region1可读写,使能MPU后再访问地址0x20006000,测试代码如下:
void mpu_overlap(void)
{
volatile uint32_t *temptr = (volatile uint32_t *)0x20006000UL;
MPU_Cmd(MPU, DISABLE);
*temptr = 0x00;
printf("%x : %x\n", (uint32_t)temptr,*temptr);
*temptr = 0xB9;
printf("%x : %x\n", (uint32_t)temptr,*temptr);
//Configure region0:0x20006000 - 0x20006FFF
//Read/write,Execution not permitted,Device memory
MPU_SelectRegion(MPU, 0);
MPU_SetRegionBase(MPU, 0x20006000UL, REGION_NON_SHAREABLE, REGION_RW_PRIV_ONLY, REGION_XN);
MPU_SetRegionLimit(MPU, 0x20006FFFUL, 0, REGION_EN);
MPU_SetRegionAttr(MPU, 0, 0);
//Configure region1:0x20005000 - 0x20007FFF
//Read/write,Execution not permitted,Device memory
MPU_SelectRegion(MPU, 1);
MPU_SetRegionBase(MPU, 0x20005000UL, REGION_NON_SHAREABLE, REGION_RW_PRIV_ONLY, REGION_XN);
MPU_SetRegionLimit(MPU, 0x20007FFFUL, 1, REGION_EN);
MPU_SetRegionAttr(MPU, 0, 1);
MPU_HfnmienaCmd(MPU, ENABLE);
MPU_PrivdefenaCmd(MPU, ENABLE);
MPU_Cmd(MPU, ENABLE);
printf("Test start:\n");
//The address of the 0x20006000 overlaps in region0 and region1.
//Accessing the address triggers a hard fault.
printf("%x : %x\n", (uint32_t)temptr,*temptr);
printf("Test over!\n");
}
仿真查看MPU配置情况和代码执行预期一致:
串口调试助手打印情况:
打印Test start之前的数据可以进行读写,使能MPU之后,因为地址0x20006000位于region0和region1的overlap区域,运行打印*temptr 的语句,就进入了HardFault,该位置不可访问,说明MPU不支持region overlap,否则对overlap区域访问时会触发HardFault。
5.3 region code execute测试
将func()函数定义在指定地址0x08007800位置,在MPU关闭时,程序中调用该函数可以正常执行,通过配置MPU将0x08007800 - 0x080087FF区域设置为region0不可执行,使能MPU后再次运行func()函数,观察测试情况。测试代码如下:
void func(void) __attribute__((section(".ARM.__AT_0x08007800")));
void func(void)
{
printf("Execute the function!\n");
}
void mpu_xn(void)
{
//Inject code at 0x08007800
typedef void (*test_func_t)(void);
test_func_t test_func = (test_func_t)0x08007801;
test_func();
MPU_Cmd(MPU, DISABLE);
//Enable MPU region0:0x08007800 - 0x080087FF
//Read-only,Execution not permitted,Device memory
MPU_SelectRegion(MPU, 0);
MPU_SetRegionBase(MPU, 0x08007800UL, REGION_NON_SHAREABLE, REGION_RO_PRIV_ONLY, REGION_XN);
MPU_SetRegionLimit(MPU, 0x080087FFUL, 0, REGION_EN);
MPU_SetRegionAttr(MPU, 0, 0);
MPU_HfnmienaCmd(MPU, ENABLE);
MPU_PrivdefenaCmd(MPU, ENABLE);
MPU_Cmd(MPU, ENABLE);
printf("Test start:\n");
//The test_func of 0x08007800 cannot be executed after MPU is enabled.
//The hardfault will be triggered when the code is executed here.
test_func();
printf("Test over!\n");
}
仿真查看MPU配置情况和代码执行预期一致:
串口调试助手打印情况:
打印Test start之前调用func()函数一次,执行正常。使能MPU之后,再次调用func()函数,就进入了HardFault,该代码不可执行。
6 小结
MPU为存储保护单元,它位于存储器内部的一个可编程的区域,定义了存储器的属性和访问权限,试图访问非法或者不允许的内存地址则会触发HardFault异常。MPU能够提高嵌入式系统的健壮性,使得系统更加安全。实际应用中根据具体的项目需要,选择MPU是默认配置还是需要更改一些配置,这样才能使应用更加符合要求。
作者:灵动MM32MCU
文章来源:灵动MM32MCU
推荐阅读
- 灵动微课堂 (第270讲)|CLASS B内部时钟检测在MM32SPIN0280上的实现
- MicroPython应用基础-准备基于MM32F5的MicroPython开发板
- 使用MM32L0130和HYS1254的电子秤方案验证
- 利用DMA的触发循环实现对eTMR的PWM周期计数
- 手工打造基于MM32F5微控制器的MicroPython开发板
更多MM32F5系列资料请关注灵动MM32 MCU专栏。如想进行MM32相关芯片技术交流,请添加极术小姐姐微信(id:aijishu20)加入微信群。