MPU:鴻蒙輕核心的任務棧的溢位檢察官

華為雲開發者社群發表於2021-10-05
摘要:MPU(Memory Protection Unit,記憶體保護單元)把記憶體對映為一系列記憶體區域,定義這些記憶體區域的維洲,大小,訪問許可權和記憶體熟悉資訊。

本文分享自華為雲社群《鴻蒙輕核心M核原始碼分析系列十六 MPU記憶體保護單元》,作者: zhushy。

MPU(Memory Protection Unit,記憶體保護單元)把記憶體對映為一系列記憶體區域,定義這些記憶體區域的維洲,大小,訪問許可權和記憶體熟悉資訊。MPU支援對每個記憶體區域進行獨立的屬性設定,允許記憶體區域重,可以匯出記憶體屬性。有關MPU的詳細資訊可以參考官方資料站點,比如對應Cortex-M3的文件位置為:https://developer.arm.com/doc...

在鴻蒙輕核心中,MPU用於任務棧的溢位檢測。本文主要分析鴻蒙輕核心MPU模組的原始碼。本文中所涉及的原始碼,以OpenHarmony LiteOS-M核心為例,均可以在開源站點https://gitee.com/openharmony... 獲取。鴻蒙輕核心支援的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/doc...,特別是上述頁面的表格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/doc...,檢視頁面上的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/doc...。⑵處程式碼使能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/doc...

⑷處表示MPU的資料記憶體區域(MPU data regions)數量不為空時,執行⑸處程式碼更新MPU記憶體區域編號暫存器(MPU Region Number Register
)為指定的記憶體區域編號,詳細的資訊可以參考https://developer.arm.com/doc...。然後執行⑹處程式碼更新MPU記憶體區域屬性和大小暫存器(MPU Region Attribute and Size Register
),詳細可以參考https://developer.arm.com/doc...。⑺處把全域性變數陣列中指定的區域編號設定為未使用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/doc...瞭解更多。⑹處計算屬性和大小暫存器的數值。⑺處如果指定的記憶體區域被使用,直接返回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;
}

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章