2

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

 2 years ago
source link: https://my.oschina.net/u/4526289/blog/5270811
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

MPU:鸿蒙轻内核的任务栈的溢出检察官 - 华为云开发者社区的个人空间 - OSCHINA - 中文开源技术交流社区

摘要:MPU(Memory Protection 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模块的源码。本文中所涉及的源码,以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。查看页面上的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 使能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;
}

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

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;
}

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;
}

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK