鸿蒙内核M核源码分析系列十六 MPU内存保护单元

网友投稿 634 2022-05-30

鸿蒙轻内核M核源码分析系列十六 MPU内存保护单元

MPU(Memory Protection Unit,内存保护单元)把内存映射为一系列内存区域,定义这些内存区域的维洲,大小,访问权限和内存熟悉信息。MPU支持对每个内存区域进行独立的属性设置,允许内存区域重, 可以导出内存属性。有关MPU的详细信息可以参考官方资料站点,比如对应Cortex-M3的文档位置为:https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/optional-memory-protection-unit。

在鸿蒙轻内核中,MPU用于任务栈的溢出检测。本文主要分析鸿蒙轻内核MPU模块的的源码。本文中所涉及的源码,以OpenHarmony LiteOS-M内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。鸿蒙轻内核支持的ARM Cortex-M芯片架构都支持MPU的,代码都是一样的,以kernel\arch\arm\cortex-m4\gcc\los_mpu.c为例进行讲解。

1、MPU枚举、结构体定义和常用宏定义

1.1 MPU枚举、结构体定义

在文件kernel\arch\include\los_mpu.h定义MPU相关的结构体。⑴处定义MPU内存区域的访问权限,有关访问权限可以访问官网https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/optional-memory-protection-unit/mpu-access-permission-attributes,特别是上述页面的表格Table 4.47. AP encoding了解更多。⑵处定义MPU的是否可执行属性枚举,⑶处定义MPU内存区域是否可以共享属性枚举,⑷定义内存区域的类型属性枚举,⑸处的结构体用于定义MPU内存区域。

⑴ typedef enum { MPU_RW_BY_PRIVILEGED_ONLY = 0, MPU_RW_ANY = 1, MPU_RO_BY_PRIVILEGED_ONLY = 2, MPU_RO_ANY = 3, } MpuAccessPermission; ⑵ typedef enum { MPU_EXECUTABLE = 0, MPU_NON_EXECUTABLE = 1, } MpuExecutable; ⑶ typedef enum { MPU_NO_SHARE = 0, MPU_SHARE = 1, } MpuShareability; ⑷ typedef enum { MPU_MEM_ON_CHIP_ROM = 0, MPU_MEM_ON_CHIP_RAM = 1, MPU_MEM_XIP_PSRAM = 2, MPU_MEM_XIP_NOR_FLASH = 3, MPU_MEM_SHARE_MEM = 4, } MpuMemType; ⑸ typedef struct { UINT32 baseAddr; UINT64 size; /* armv7 size == 2^x (5 <= x <= 32) 128B - 4GB */ MpuAccessPermission permission; MpuExecutable executable; MpuShareability shareability; MpuMemType memType; } MPU_CFG_PARA;

1.2 MPU宏

MPU外设的一些宏定义有HAL Drivers定义,比如对于Cortex-M4,位置为Drivers\CMSIS\Core\Include\core_cm4.h。MPU结构体定义如下,关于MPU寄存器的详细信息可以访问https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/optional-memory-protection-unit,查看页面上的Table 4.38. MPU registers summary。下文在讲解代码时会涉及MPU的各个寄存器。

#if defined (__MPU_PRESENT) && (__MPU_PRESENT == 1U) #define MPU_BASE (SCS_BASE + 0x0D90UL) /*!< Memory Protection Unit */ #define MPU ((MPU_Type *) MPU_BASE ) /*!< Memory Protection Unit */ #endif

鸿蒙轻内核M核源码分析系列十六 MPU内存保护单元

另外,MPU支持8个内存区域,kernel\arch\arm\cortex-m4\gcc\los_mpu.c文件中定义的宏如下:

#define MPU_MAX_REGION_NUM 8

2、MPU常用操作

MPU常用操作函数包含使能MPUHalMpuEnable、失能MPUHalMpuDisable,设置指定的内存区域属性HalMpuSetRegion,失能指定的内存区域HalMpuDisableRegion和获取未使用的内存区域编号HalMpuUnusedRegionGet。

2.1 使能MPUHalMpuEnable

该函数使能MPU功能,⑴处对MPU控制寄存器MPU Control Register进行操作,通过对寄存器相关的bit位进行赋值来使能MPU。有关该寄存器建议详细阅读https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/optional-memory-protection-unit/mpu-control-register。⑵处代码使能MemoryFault异常。接着执行的数据同步屏障__DSB()和指令同步屏障__ISB(),详细的可以查阅ARM的DMB,DSB,ISB等指令。

VOID HalMpuEnable(UINT32 defaultRegionEnable) { UINT32 intSave = HalIntLock(); ⑴ MPU->CTRL = (MPU_CTRL_ENABLE_Msk | ((defaultRegionEnable << MPU_CTRL_PRIVDEFENA_Pos) & MPU_CTRL_PRIVDEFENA_Msk)); ⑵ SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; __DSB(); __ISB(); HalIntRestore(intSave); }

2.2 失能MPUHalMpuDisable

代码很简单,直接把MPU控制寄存器赋值为0来失能MPU功能。

VOID HalMpuDisable(VOID) { UINT32 intSave = HalIntLock(); MPU->CTRL = 0; __DSB(); __ISB(); HalIntRestore(intSave); }

2.3 失能指定的内存区域HalMpuDisableRegion

HalMpuDisableRegion函数执行后不再对指定的内存区域进行MPU保护,⑴处校验参数合法性。⑵处没有使用的MPU内存区域无法失能。⑶处获取MPU的类型寄存器,详细可以访问https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/optional-memory-protection-unit/mpu-type-register。

⑷处表示MPU的数据内存区域(MPU data regions)数量不为空时,执行⑸处代码更新MPU内存区域编号寄存器(MPU Region Number Register

)为指定的内存区域编号,详细的信息可以参考https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/optional-memory-protection-unit/mpu-region-number-register。然后执行⑹处代码更新MPU内存区域属性和大小寄存器(MPU Region Attribute and Size Register

),详细可以参考https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/optional-memory-protection-unit/mpu-region-attribute-and-size-register。⑺处把全局变量数组中指定的区域编号设置为未使用0。

UINT32 HalMpuDisableRegion(UINT32 regionId) { volatile UINT32 type; UINT32 intSave; ⑴ if (regionId >= MPU_MAX_REGION_NUM) { return LOS_NOK; } intSave = HalIntLock(); ⑵ if (!g_regionNumBeUsed[regionId]) { HalIntRestore(intSave); return LOS_NOK; } ⑶ type = MPU->TYPE; ⑷ if ((MPU_TYPE_DREGION_Msk & type) != 0) { ⑸ MPU->RNR = regionId; ⑹ MPU->RASR = 0; __DSB(); __ISB(); } ⑺ g_regionNumBeUsed[regionId] = 0; /* clear mpu region used flag */ HalIntRestore(intSave); return LOS_OK; }

2.4 设置指定的内存区域属性HalMpuSetRegion

HalMpuSetRegion函数设置指定的内存区域的属性。⑴处对参数进行合法性校验。⑵处如果MPU类型寄存器中表示的数据内存区域的数量为0,无法继续设置内嵌区域,直接返回LOS_NOK。⑶处调用函数HalMpuEncodeSize根据内存区域的实际大小值获取编码大小,该值后续会被赋值给MPU属性和大小寄存器的size位。⑷判断内存区域需要相对内存区域大小进行内存对齐,否则返回LOS_NOK。

⑸处计算基地址寄存器的数据,有关基地址寄存器(MPU Region Base Address Register),可以访问https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/optional-memory-protection-unit/mpu-region-base-address-register了解更多。⑹处计算属性和大小寄存器的数值。⑺处如果指定的内存区域被使用,直接返回LOS_NOK。⑻处设置MPU相关的寄存器,并标记该内存区域已使用。代码如下:

UINT32 HalMpuSetRegion(UINT32 regionId, MPU_CFG_PARA *para) { UINT32 RASR; UINT32 RBAR; UINT32 RNR; UINT32 encodeSize; UINT32 intSave; UINT64 size; ⑴ if ((regionId >= MPU_MAX_REGION_NUM) || (para == NULL)) { return LOS_NOK; } ⑵ if ((MPU_TYPE_DREGION_Msk & MPU->TYPE) == 0) { return LOS_NOK; } RNR = regionId; ⑶ encodeSize = HalMpuEncodeSize(para->size); if (encodeSize == 0) { return LOS_NOK; } ⑷ size = para->size - 1; /* size aligned after encode check */ if ((para->baseAddr & size) != 0) { /* base addr should aligned to region size */ return LOS_NOK; } ⑸ RBAR = para->baseAddr & MPU_RBAR_ADDR_Msk; ⑹ RASR = HalMpuGetRASR(encodeSize, para); intSave = HalIntLock(); ⑺ if (g_regionNumBeUsed[regionId]) { HalIntRestore(intSave); return LOS_NOK; } ⑻ MPU->RNR = RNR; MPU->RBAR = RBAR; MPU->RASR = RASR; __DSB(); __ISB(); g_regionNumBeUsed[regionId] = 1; /* Set mpu region used flag */ HalIntRestore(intSave); return LOS_OK; }

HalMpuEncodeSize函数根据内存区域实际大小获取size属性值,对应的计算公式为:(Region size in bytes) = 2^(SIZE+1),详细信息可以访问MPU属性和大小寄存器官网资料页面的Table 4.44. Example SIZE field values。32bytes对应4,1KB对应5,…,4GB对应31。

⑴处表示内存区域大小不能大于4GB,然后判断是否相对32字节进行内存对齐。⑵处先右移2位,然后while循环,执行⑶每向右循环一位,size属性大小增加1。

STATIC UINT32 HalMpuEncodeSize(UINT64 size) { UINT32 encodeSize = 0; ⑴ if (size > SIZE_4G_BYTE) { return 0; } if ((size & 0x1F) != 0) { /* size should aligned to 32 byte at least. */ return 0; } ⑵ size = (size >> 2); while (size != 0) { if (((size & 1) != 0) && ((size & 0xFFFFFFFE) != 0)) { /* size != 2^x (5 <= x <= 32) 128B - 4GB */ return 0; } ⑶ size = (size >> 1); encodeSize++; } return encodeSize; }

HalMpuGetRASR根据size属性值和配置参数计算属性和大小寄存器的值。⑴处根据配置的访问权限计算AP(ACCESS permission),然后计算属性和大小寄存器的值,最后执行⑶给寄存器赋值。

STATIC UINT32 HalMpuEncodeAP(MpuAccessPermission permission) { UINT32 ap; switch (permission) { case MPU_RW_BY_PRIVILEGED_ONLY: ap = MPU_AP_RW_USER_FORBID; break; case MPU_RW_ANY: ap = MPU_AP_RW_USER_RW; break; case MPU_RO_BY_PRIVILEGED_ONLY: ap = MPU_AP_RO_USER_FORBID; break; case MPU_RO_ANY: ap = MPU_AP_RO_USER_RO; break; default: ap = MPU_AP_RW_USER_RW; break; } return ap; } STATIC VOID HalMpuRASRAddMemAttr(MPU_CFG_PARA *para, UINT32 *RASR) { BOOL cachable = 0; BOOL buffable = 0; switch (para->memType) { case MPU_MEM_ON_CHIP_ROM: case MPU_MEM_ON_CHIP_RAM: cachable = 1; buffable = 0; break; case MPU_MEM_XIP_PSRAM: cachable = 1; buffable = 1; break; case MPU_MEM_XIP_NOR_FLASH: cachable = 0; buffable = 1; break; default: break; } (*RASR) |= ((cachable << MPU_RASR_C_Pos) | (buffable << MPU_RASR_B_Pos)); } STATIC UINT32 HalMpuGetRASR(UINT32 encodeSize, MPU_CFG_PARA *para) { UINT32 RASR; UINT32 ap; ⑴ ap = HalMpuEncodeAP(para->permission); RASR = MPU_RASR_ENABLE_Msk; RASR |= ((encodeSize << MPU_RASR_SIZE_Pos) & MPU_RASR_SIZE_Msk); RASR |= ((ap << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk) | ((para->executable << MPU_RASR_XN_Pos) & MPU_RASR_XN_Msk) | ((para->shareability << MPU_RASR_S_Pos) & MPU_RASR_S_Msk); ⑶ HalMpuRASRAddMemAttr(para, &RASR); return RASR; }

小结

本文带领大家一起剖析了鸿蒙轻内核的MPU模块的源代码。感谢阅读,如有任何问题、建议,都可以博客下留言给我,谢谢。

IoT 轻量级操作系统 LiteOS

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:"Deno在手,天下我有",极速获取今日热榜
下一篇:在 Linux 上保护 Apache Web 服务器的技巧
相关文章