RISC-V的PMP功能详解

2024-09-04  本文已影响0人  jin陵城外

1. PMP简介

PMP(Physical Memory Protection)是 RISC-V 架构中的一种硬件机制,用于控制不同内存区域的读、写和执行权限,主要提供对物理内存的保护和隔离,本文对PMP相关寄存器的说明以及PMP功能接口的实现与使用进行介绍。

2. PMP寄存器说明

PMP的使用是通过设置一对PMP寄存器操作的,包括一个是地址寄存器pmpaddr,用于设置受保护的地址区域,和一个配置寄存器pmpcfg,用于设置受保护区域的读、写、执行权限以及地址匹配模式,每一对PMP寄存器称之为一个PMP条目(entry),支持的条目数量和不同CPU硬件实现相关,通常entry数量是8、16、32和64不等,具体数量需要根据对应CPU的手册确定,软件不能使用超过CPU规定的entry数量,本文基于32位CPU的16组entry进行说明。

2.1 PMP配置寄存器(pmpcfg)

PMP每一个entry的配置主要有8bit组成,其定义如下:

bit             width          field_name         desc                                 note

0                 1                    R                  Read

1                 1                    W                 Write

2                 1                    X                  Execute

3-4              2                    A                   Address matching            00:  OFF(disable)

                                                                                                      01:TOR (top of range)

                                                                                                      10:NA4

                                                                                                       11:  NAPOT

5-6              2                                          Reserved Fix 0

7                 1                    L                     Lock

虽然每一个配置只有8bit,但是risc-v提供的pmpcfg寄存器在32bit CPU上是32bit的,为了充分利用每一个bit,一个32bit的pmpcfg寄存器包含4个pmpcfg配置,其结构如下:

pmpcfg0 = (pmp3cfg<<24)| (pmp2cfg<<16) | (pmp1cfg<<8) | pmp0cfg;

pmpcfg1 = (pmp7cfg<<24)| (pmp6cfg<<16) | (pmp5cfg<<8) | pmp4cfg;

pmpcfg2 = (pmp11cfg<<24)| (pmp10cfg<<16) | (pmp9cfg<<8) | pmp8cfg;

pmpcfg3 = (pmp15cfg<<24)| (pmp14cfg<<16) | (pmp13cfg<<8) | pmp12cfg;

1. R (Read)

    当R==1时,表示该PMP entry对应的地址区域可读,反之不可读(如果读则触发exception);

2. W (Write)

    当W==1时,表示该PMP entry对应的地址区域可写,反之不可写(如果写则触发exception);

3. X (Execute)

    当X==1时,表示该PMP entry对应的地址区域可执行,反之不可执行(如果执行则触发exception);

4. A (Address matching)

    00(OFF):说明这个条目是disable的,不会去匹配任何地址;

    01:TOR (top of range):顶部匹配模式,需要使用2个PMP条目定义一块内存保护区域,比如entry[i-1]和entry[i],同时entry[i-1]的pmpaddr[i-1]必须小于entry[i]的pmpaddr[i],仅仅需要把entry[i]的pmpcfg[i]设置为TOR模式即可(不用考虑entry[i-1]中pmpcfg[i-1]的权限配置),如果i=0,则小于pmpaddr[0]的任何内存地址的访问权限都受entry[0]的配置影响,如果pmpaddr[i-1]>= pmpaddr[i],并且pmpcfg[i]设置TOR模式,则entry[i]匹配不到任何地址;

    10:NA4(naturally aligned four-byte region):NA4是NAPOT的特殊情况,既受保护的内存大小刚好为4byte;

    11:  NAPOT(naturally aligned power-of-two region):表示内存大小必须是2的次幂大小,且最小值为8bytes;

 5. L(Lock):如果Lock位被置1,则PMP的entry就会被锁定,写PMP的配置寄存器和地址寄存器都会被忽略,只有复位硬件才能解除锁定,当lock为0时,只有在用户模式和超级管理员模式下访问PMP配置的内存区域才会受影响,机器模式不受影响,此时机器模式可以访问任何内存区域,但是当lock为1时,所有模式下访问PMP配置的内存区域都会受影响,包括机器模式;

2.2 PMP地址寄存器(pmpaddr)

risc-v中pmpaddr定义的是34bit物理地址的高32bit,所以设置pmpaddr的时候需要将传入的实际物理内存地址左移2bit,即phy_addr << 2, 同时由于TOR模式可以通过2个entry之间的pmpaddr得到受保护的内存大小,而NA4是固定的4bytes大小,故不用在pmpaddr中传入内存大小,但是对于NAPOT模式,除了需要传入物理内存地址,还需要传入受保护的内存大小,公式如下:

pmpaddr = (phy_addr << 2) | ((phy_size>>3) - 1);//only for NAPOT mode

3. PMP接口代码实现

//PMP条目数量是根据具体CPU硬件实现来确定的,这里假设是16个entry    #define ENTRY_NUM  16

3.1 pmpaddr接口函数实现

void _set_pmpaddrx(uint32_t index, uint32_t pmp_addr)

{

    switch(index) {

        case 0:

            //基于编译器的risc-v汇编自己实现pmpaddr0 = pmp_addr

            break;       

        case 1:

            //基于编译器的risc-v汇编自己实现pmpaddr1 = pmp_addr

            break;

        case 2:

            //基于编译器的risc-v汇编自己实现pmpaddr2 = pmp_addr

            break;

        case 3:

            //基于编译器的risc-v汇编自己实现pmpaddr3 = pmp_addr

            break;       

        case 4:

            //基于编译器的risc-v汇编自己实现pmpaddr4 = pmp_addr

            break;       

        case 5:

            //基于编译器的risc-v汇编自己实现pmpaddr5 = pmp_addr

            break;       

        case 6:

            //基于编译器的risc-v汇编自己实现pmpaddr6 = pmp_addr

            break;

        case 7:

            //基于编译器的risc-v汇编自己实现pmpaddr7 = pmp_addr

            break;

        ...

        case 15:

            //基于编译器的risc-v汇编自己实现pmpaddr15 = pmp_addr

            break;

        default:

            break;

    }

}

//entry_index是PMP条目序号,phy_addr是物理内存地址,phy_size是需要保护的内存大小,mode是地址匹配模式

void set_pmpaddr(uint32_t entry_index, uint32_t phy_addr, uint32_t phy_size, uint8_t mode)

{

    uint32_t index = 0;

    uint32_t addr = 0;

    uint32_t size = 0;

    uint32_t pmp_addr = 0;

    /* 1. set pmp entry index */

    index = entry_index;

    /* 2. set memory address */

    addr = phy_addr >> 2;

    /* 3. set memory size */

    if(mode == NA4 || mode == TOR) {

      pmp_addr = addr;

    } else {

      size = (phy_size >> 3) -1;

      pmp_addr = addr | size;

    }

    /* 4. set pmpaddr register */

    _set_pmpaddrx(index, pmp_addr);

}

注意:物理地址phy_addr低位必须足够放得下phy_size的空间,即phy_addr的低phy_size的位置必须是0,同时对于NAPOT模式下phy_size必须大于等于8byte且是2的次幂;

3.2 pmpcfg接口函数实现

uint32_t _get_pmpcfgx(uint32_t index)

{

    uint32_t pmpcfg = 0;

    switch(index) {

        case 0:

            //基于编译器的risc-v汇编自己实现pmpcfg = pmpcfg0

            break;

        case 1:

            //基于编译器的risc-v汇编自己实现pmpcfg = pmpcfg1

            break;

        case 2:

            //基于编译器的risc-v汇编自己实现pmpcfg = pmpcfg2   

            break;

        case 3:

            //基于编译器的risc-v汇编自己实现pmpcfg = pmpcfg3

            break;

        default:

            break;     

    }

    return pmpcfg;

}

void _set_pmpcfgx(uint32_t index, uint32_t pmpcfg)

{

    switch(index) {

        case 0:

            //基于编译器的risc-v汇编自己实现pmpcfg0 = pmpcfg

            break;

        case 1:

            //基于编译器的risc-v汇编自己实现pmpcfg1 = pmpcfg

            break;

        case 2:

            //基于编译器的risc-v汇编自己实现pmpcfg2 = pmpcfg   

            break;

        case 3:

            //基于编译器的risc-v汇编自己实现pmpcfg3 = pmpcfg

            break;

        default:

            break;     

    }

}

//entry_index是PMP条目序号,pmpcfg是pmp的配置

void set_pmpcfg(uint32_t entry_index, uint8_t pmpcfg)

{

    uint32_t index = 0;

    uint32_t pmp_cfgx = 0;

    uint32_t offset= 0;

    uint32_t pmpcfg_num = ENTRY_NUM/4;

    uint32_t offset = 0;

    uint32_t mask = 0;

    /* 1. set pmp index */

    index = entry_index >> 2;

    /* 2. update pmpcfgx value */

    offset= (entry_index & (pmpcfg_num - 1)) << 3;

    mask = ~(0xff << offset);

    pmp_cfgx = _get_pmpcfgx(index & (pmpcfg_num - 1));

    pmp_cfgx |= ((pmpcfg << offset) & (~mask));

    /* 3. set pmpcfg resiter */

    _set_pmpcfgx(index, pmp_cfgx);

}

注意:由于一个pmpcfgx中包含4组pmpxcfg(pmp[i+3]cfg /pmp[i+2]cfg /pmp[i+1]cfg /pmp[i]cfg),所以传入的entry_index需要右移2bit,同时在设置pmpxcfg到对应的pmpcfgx寄存器的时候,需要先读取当前pmpcfgx寄存器的值,再将pmpxcfg的值改写到entry_index对应的pmpcfgx寄存器的8bit上;

4. PMP注意事项

    1. [endif]PMP entry的寄存器只有在机器模式下才能设置;

    2. [endif]默认情况下(Lock为0),PMP条目对内存区域的保护只针对用户模式和超级管理员模式,但是当Lock为1时,对机器模式也同样起作用;

    3. [endif]在机器模式下,可以通过把mstatus.mprv寄存器设置为1,把mstatus.mpp的值设置为用户模式和超级管理员模式对应的值,从而让CPU按照用户模式和超级管理员模式来进行PMP控制;

    4. [endif]当同时设置多个PMP条目时,序号越小,优先级越高,entry[0]> entry[1]> entry[2]>…entry[64];

    5. Lock为1,机器模式下,没有匹配到任何PMP条目的内存区域,默认具有可读可写可执行权限;

    6. 用户模式或者超级管理员模式下(包括通过mstatus.mprv和mstatus.mpp设置的),没有匹配到任何PMP条目的内存地址,默认不可访问,否则报exception;

    7. PMP保护的区域不仅仅是RAM区域,还包括总线上支持的合法区域,如各个外设的地址区域,flash区域等;

上一篇 下一篇

猜你喜欢

热点阅读