写点什么

MPU:鸿蒙轻内核的任务栈的溢出检察官

发布于: 刚刚

摘要:MPU(MemoryProtection Unit,内存保护单元)把内存映射为一系列内存区域,定义这些内存区域的维洲,大小,访问权限和内存熟悉信息。

 

本文分享自华为云社区《鸿蒙轻内核M核源码分析系列十六 MPU内存保护单元》,作者: zhushy。

 

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


在鸿蒙轻内核中,MPU 用于任务栈的溢出检测。本文主要分析鸿蒙轻内核 MPU 模块的源码。本文中所涉及的源码,以 OpenHarmonyLiteOS-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 外设的一些宏定义有 HALDrivers 定义,比如对于 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
复制代码


另外,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 使能 MPU


HalMpuEnable 该函数使能 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 失能 MPU


HalMpuDisable 代码很简单,直接把 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 dataregions)数量不为空时,执行⑸处代码更新 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 andSize 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 AddressRegister),可以访问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;}
复制代码

2.4.1 HalMpuEncodeSize 根据内存区域实际大小获取 size 属性值


HalMpuEncodeSize 函数根据内存区域实际大小获取 size 属性值,对应的计算公式为:(Regionsize 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;}
复制代码


2.4.2 HalMpuGetRASR 根据 size 属性值和配置参数计算属性和大小寄存器的值


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;}
复制代码


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 2
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
MPU:鸿蒙轻内核的任务栈的溢出检察官