armv8m(cortex m33) MPU实战

文章目录

  • 1 MPU
    • 1.1 Memory attributes summary
    • 1.2 MPU寄存器
      • 1.2.1 MPU Type Register
      • 1.2.2 MPU Control Register
      • 1.2.3 MPU Region Number Register
      • 1.2.4 MPU Region Base Address Register
      • 1.2.5 MPU Region Limit Address Register
      • 1.2.6 MPU Memory Attribute Indirection Registers 0 and 1
  • 2 MPU 代码
    • 2.1环境搭建
    • 2.2 MPU 源码
      • 2.2.1 armv8m_mpu.h
      • 2.2.2 只读region测试
      • 2.2.3 地址overlap测试
      • 2.2.4 执行权限测试

1 MPU

armv8m 上的MPU 有8个region,每一个region都有起始地址,结束地址,访问权限和内存属性。每一个region都有单独的属性。
本文仅以secure MPU为例讲解MPU。
armv8m 上的MPU和armv7m的MPU有一点不同, v8m的MPU不支持overlap,如果一个地址同时出现在两个不同的region中,会导致bus fault。

1.1 Memory attributes summary

Memory Type Shareability Other attributes Description
Device-nGnRnE Sharable - Used to access memory mapped peripherals.All accesses to DevicenGnRnE memory occur in program order. All regions are assumed to be shared
Device-nGnRE Sharable - Used to access memory mapped peripherals.Weaker ordering thanDevice-nGnRnE.
Device-nGRE Sharable - Used to access memory mapped peripherals.Weaker ordering thanDevice-nGnRE.
Device-GRE Sharable - Used to access memory mapped peripherals.Weaker ordering thanDevice-nGRE.
Normal Sharable Non-cacheable Write-Through Cacheable Write-Back Cacheable Normal memory that is shared between several processors.
Normal Non-Shareable Non-cacheable Write-Through Cacheable Write-Back Cacheable Normal memory that only a single processor uses.

1.2 MPU寄存器

Address Name Access Type Reset Value
0xE000ED90 MPU_TYPE RO 该寄存器的值是固定的,取决于SOC实现
0xE000ED94 MPU_CTRL RW 0x00000000
0xE000ED98 MPU_RNR RW 未知
0xE000ED9C MPU_RBAR RW 未知
0xE000EDA0 MPU_RLAR RW 未知
0xE000EDA4 MPU_RBAR_A RW 未知
0xE000EDA8 MPU_RBAR_A RW 未知
0xE000EDC0 MPU_MAIR0 RW 未知
0xE000EDC4 MPU_MAIR1 RW 未知

1.2.1 MPU Type Register

MPU_TYPE寄存器用来表示MPU是否存在以及它支持多少个region。

Bits Name Function
[31:16] - 保留位
[15:8] REGION 0x0-MPU没有region支持
0x8当前MPU 支持8个REGION
[7:1] - 保留位
[0] SEPARATE 0

1.2.2 MPU Control Register

MPU_CTRL用来

  • 使能MPU
  • 使能backgroup map
  • 使能NMI中MPU是否有效
Bits Name Function
[31:3] - 保留位
[2] PRIVDEFENA 使能特权级软件使用default memory map
0 - 关闭default memory map。任何访问没有在region中描述的都会产生bus fault。
1 - 使能default memory map,当它使能后,访问没有在region中memory会使用默认的memory map
[1] HFNMIENA 使HardFault和NMI时能MPU在
0 - 在HardFault和NMI时, MPU无效
1 - 在HardFault和NMI时, MPU仍然有效
[0] ENABLE 使能MPU
0 - 关闭MPU
1 - 打开MPU

1.2.3 MPU Region Number Register

MPU_RNR用来选择region,在访问MPU_RBAR和MPU_RLAR之前,必须先写入MPU_RNR来选择region

Bits Name Function
[31:8] - 保留位
[7:0]REGION REGION索引,如果选择REGION1, 则设为1

1.2.4 MPU Region Base Address Register

MPU_RBAR定义了MPU region的起始地址

Bits Name Function
[31:5] BASE REGION 基地址
[4:3] SH 定义了Normal的Shareability属性
0b00 Non-shareable.
0b01 UNPREDICATABLE
0b10 Outer shareable.
0b11 Inner shareable
[2:1] AP 访问属性
0b00 特权级代码可读写.
0b01 特权级代码可读写
0b10 特权级代码只读.
0b11 所有代码只读
[0] XN 0 可执行Memory
1 不可执行Memory

1.2.5 MPU Region Limit Address Register

MPU_RLAR定义了REGION的上限地址以及REGION属性选择

Bits Name Function
[31:5] LIMIT REGION 上限地址
[4] - 保留位
[3:1] AttrIndx 属性索引号
[0] EN 0 不使能REGION
1 使能REGION

1.2.6 MPU Memory Attribute Indirection Registers 0 and 1

MPU_MAIR0

Bits Name Function
[31:24] Attr3 REGION3属性
[23:16] Attr2 REGION2属性
[15:8] Attr1 REGION1属性
[7:0] Attr0 REGION0属性

MPU_MAIR1

Bits Name Function
[31:24] Attr7 REGION7属性
[23:16] Attr6 REGION6属性
[15:8] Attr5 REGION5属性
[7:0] Attr4 REGION4属性

每一个REGION属性MARI_ATTR占8位,如果MAIR_ATTR[7:4]为0:那MAIR_ATTR定义如下:

Bits Name Function
[3:2] Device 0b00 Device-nGnRnE
0b01 Device-nGnRE
0b10 Device-nGRE
0b11 Device-GRE

如果MAIR_ATTR[7:4]不为0, 那么MAIR_ATTR定义如下

Bits Name Function
[7:4] Outer Outer attributes. Specifies the Outer memory attributes. The possible values of this field are:
0b0000 -Device memory.
00RW -Normal memory, Outer write-through transient (RW is not 00).
0b0100 -Normal memory, Outer non-cacheable.
01RW-Normal memory, Outer write-back transient (RW is not 00).10RW Normal memory, Outer write-through non-transient.
11RW -Normal memory, Outer write-back non-transient.
R and W specify the outer read and write allocation policy: 0 = do not allocate, 1 = allocate.
[3:0] Inner Inner attributes. Specifies the Inner memory attributes. The possible values of this field are:
0b0000 -UNPREDICTABLE.
00RW- Normal memory, Inner write-through transient (RW is not 00).
0b0100- Normal memory, Inner non-cacheable.
01RW -Normal memory, Inner write-back transient (RW is not 00).
10RW -Normal memory, Inner write-through non-transient.
11RW -Normal memory, Inner write-back non-transient.
R and W specify the outer read and write allocation policy: 0 = do not allocate, 1 = allocate.

2 MPU 代码

2.1环境搭建

代码git
MPS2开发板手册

安装qemu-system-arm, 确保该版本下有cortex m33的仿真板

mps2-an385           ARM MPS2 with AN385 FPGA image for Cortex-M3
mps2-an505           ARM MPS2 with AN505 FPGA image for Cortex-M33
mps2-an511           ARM MPS2 with AN511 DesignStart FPGA image for Cortex-M3
mps2-an521           ARM MPS2 with AN521 FPGA image for dual Cortex-M33

安装工具链armv8l-linux-gnueabihf-
运行命令:

make;make qemu

log:

qemu-system-arm -machine mps2-an505 -cpu cortex-m33 \
                    -m 4096 \
		    -nographic \
                    -kernel kernel.elf 
qemu-system-arm: invalid accelerator kvm
qemu-system-arm: falling back to tcg
hello cortex m33
test_func_print entry
test_armv8m_xn:mpu setup done
default_exception_handler entry

2.2 MPU 源码

2.2.1 armv8m_mpu.h

#ifndef _ARMV8M_MPU_H_
#define _ARMV8M_MPU_H_

#include 
#include 

typedef struct armv8m_mpu_tag {
    volatile uint32_t mpu_type;
    volatile uint32_t mpu_ctrl;
    volatile uint32_t mpu_rnr;
    volatile uint32_t mpu_rbar;
    volatile uint32_t mpu_rlar;
    volatile uint32_t reserved[7];
    volatile uint32_t mpu_mair0;
    volatile uint32_t mpu_mair1;
} armv8m_mpu_t;

#define MPU_CTRL_ENABLE_SHITF       0UL
#define MPU_CTRL_ENABLE_MASK        (1UL << MPU_CTRL_ENABLE_SHITF)

#define MPU_CTRL_HFNMIENA_SHIFT     1UL
#define MPU_CTRL_HFNMIENA_MASK      (1UL << MPU_CTRL_HFNMIENA_SHIFT)

#define MPU_CTRL_PRIVDEFENA_SHIFT   2UL
#define MPU_CTRL_PRIVDEFENA_MASK    (1UL << MPU_CTRL_PRIVDEFENA_SHIFT)

#define MPU_RNR_REGION_SHIFT        0UL
#define MPU_RNR_REGION_MASK         (0xFFUL << MPU_RNR_REGION_SHIFT)

#define MPU_CTRL_RBAR_BASE_SHIFT    0x5UL
#define MPU_CTRL_RBAR_BASE_MASK     0xFFFFFFE0UL

#define MPU_CTRL_RBAR_SH_SHIFT      3UL
#define MPU_CTRL_RBAR_SH_MASK       (0x3UL << MPU_CTRL_RBAR_SH_SHIFT)

#define MPU_CTRL_RBAR_AP_SHIFT      1UL
#define MPU_CTRL_RBAR_AP_MASK       (0x3UL << MPU_CTRL_RBAR_AP_SHIFT)

#define MPU_CTRL_RBAR_XN_SHIFT      0UL
#define MPU_CTRL_RBAR_XN_MASK       (1UL << MPU_CTRL_RBAR_XN_SHIFT)

#define MPU_CTRL_RLAR_LIMIT_SHIFT   5UL
#define MPU_CTRL_RLAR_LIMIT_MASK    0xFFFFFFE0UL

#define MPU_CTRL_RLAR_ATTRIDX_SHIFT 1UL
#define MPU_CTRL_RLAR_ATTRIDX_MASK  (0x7UL << MPU_CTRL_RLAR_ATTRIDX_SHIFT)

#define MPU_CTRL_RLAR_EN_SHIFT      0UL
#define MPU_CTRL_RLAR_EN_MASK       (1UL << MPU_CTRL_RLAR_EN_SHIFT)
#define REGION_NON_SHAREABLE        0
#define REGION_UNPREDICTABLE        1
#define REGION_OUTER_SHAREABLE      2
#define REGION_INNER_SHAREABLE      3

#define REGION_RW_PRIV_ONLY         0
#define REGION_RW_ANY               1
#define REGION_RO_PRIV_ONLY         2
#define REGION_RO_ANY               3

#define REGION_X                    0
#define REGION_XN                   1

#define REGION_DISABLE              0
#define REGION_EN                   1

static inline void mpu_disable(armv8m_mpu_t *mpu)
{
    mpu->mpu_ctrl &= ~MPU_CTRL_ENABLE_MASK;
    __asm("dsb");
    __asm("isb");
}

static inline void mpu_enable(armv8m_mpu_t *mpu)
{
    mpu->mpu_ctrl |= MPU_CTRL_ENABLE_MASK;
    __asm("dsb");
    __asm("isb");
}

static inline void mpu_hfnmiena_enable(armv8m_mpu_t *mpu)
{
    mpu->mpu_ctrl |= MPU_CTRL_HFNMIENA_MASK;
}
static inline void mpu_hfnmiena_disable(armv8m_mpu_t *mpu)
{
    mpu->mpu_ctrl &= ~MPU_CTRL_HFNMIENA_MASK;
}

static inline void mpu_privdefena_enable(armv8m_mpu_t *mpu)
{
    mpu->mpu_ctrl |= MPU_CTRL_PRIVDEFENA_MASK;
}

static inline void mpu_privdefena_disable(armv8m_mpu_t *mpu)
{
    mpu->mpu_ctrl &= ~MPU_CTRL_PRIVDEFENA_MASK;
}

static inline void mpu_select_region(armv8m_mpu_t *mpu, uint8_t region)
{
    mpu->mpu_rnr |= ((region << MPU_RNR_REGION_SHIFT) & MPU_RNR_REGION_MASK);
}

static inline void mpu_set_region_base(armv8m_mpu_t *mpu, uint32_t base,
                        uint8_t share_att, uint8_t ap_att, uint32_t is_xn)
{
    mpu->mpu_rbar = 0;
    mpu->mpu_rbar = (base & MPU_CTRL_RBAR_BASE_MASK) |
                    ((share_att << MPU_CTRL_RBAR_SH_SHIFT) & MPU_CTRL_RBAR_SH_MASK) |
                    ((ap_att << MPU_CTRL_RBAR_AP_SHIFT) & MPU_CTRL_RBAR_AP_MASK) |
                    (is_xn & MPU_CTRL_RBAR_XN_MASK);
}

static inline void mpu_set_region_limit(armv8m_mpu_t *mpu, uint32_t limit,
                        uint8_t att_idx, uint8_t en)
{
    mpu->mpu_rlar = 0;
    mpu->mpu_rlar = (limit & MPU_CTRL_RLAR_LIMIT_MASK) |
                     ((att_idx << MPU_CTRL_RLAR_ATTRIDX_SHIFT) & MPU_CTRL_RLAR_ATTRIDX_MASK) |
                     (en & MPU_CTRL_RLAR_EN_MASK);
}

        mpu->mpu_mair1 &= ~(0xFF << ((idx - 4) * 8));
        mpu->mpu_mair1 |= (attr << ((idx - 4) * 8));
    }
}

#endif

2.2.2 只读region测试

//测试MPU是否关闭region写权限
void test_armv8m_mpu_write()
{
    volatile uint32_t *temp_addr = (volatile uint32_t *)0x30001000UL;
    //关闭MPU前可以在Memory进行读写
    easy_printf("0x30001000:%x\n", *temp_addr);
    *temp_addr = 0x1;
    easy_printf("0x30001000:%x\n", *temp_addr);

    armv8m_mpu_t *mpu = (armv8m_mpu_t *)0xE000ED90;
    mpu_disable(mpu);	//关闭MPU
    mpu_select_region(mpu, 0);	//设置region0
    //设置region0基地址0x30000000,只读不可执行
    mpu_set_region_base(mpu, 0x30000000UL, REGION_NON_SHAREABLE, REGION_RO_PRIV_ONLY, REGION_XN); 
    //设置region0上限为0x30001FFFF, region0使用attr0,并且使能region
    mpu_set_region_limit(mpu, 0x30001FFFUL, 0, REGION_EN);
    //设置region0属性为device memory
    mpu_set_region_attr(mpu, 0, 0); /*device memory*/
    //打开MPU
    mpu_hfnmiena_disable(mpu);
    mpu_privdefena_enable(mpu);
    mpu_enable(mpu);
    easy_printf("%s:mpu setup done\n", __func__);
	//打开MPU后测试是否具有写权限
    easy_printf("0x30001000:%x\n", *temp_addr);
    //程序执行到这会触发hard fault
    *temp_addr = 0x2;
    easy_printf("0x30001000:%x\n", *temp_addr);
    easy_printf("%s done\n", __func__);
}

执行log:

qemu-system-arm: invalid accelerator kvm
qemu-system-arm: falling back to tcg
hello cortex m33
0x30001000:00000000
0x30001000:00000001
test_armv8m_mpu_write:mpu setup done
0x30001000:00000001
default_exception_handler entry

2.2.3 地址overlap测试

oid test_armv8m_mpu_overlap()
{
    volatile uint32_t *temp_addr = (volatile uint32_t *)0x30001000UL;
    easy_printf("0x30001000:%x\n", *temp_addr);
    *temp_addr = 0x1;
    easy_printf("0x30001000:%x\n", *temp_addr);

    armv8m_mpu_t *mpu = (armv8m_mpu_t *)0xE000ED90;
    mpu_disable(mpu);

	//设置region0:0x30000000 - 0x30001FFF
	//可读可写,不可执行,device memory
    mpu_select_region(mpu, 0);
    mpu_set_region_base(mpu, 0x30000000UL, REGION_NON_SHAREABLE, REGION_RW_PRIV_ONLY, REGION_XN);
    mpu_set_region_limit(mpu, 0x30001FFFUL, 0, REGION_EN);
    mpu_set_region_attr(mpu, 0, 0); /*device memory*/
	//设置region1:0x30001000 - 0x30001FFF
	//可读可写,不可执行,device memory
    mpu_select_region(mpu, 1);
    mpu_set_region_base(mpu, 0x30001000UL, REGION_NON_SHAREABLE, REGION_RW_PRIV_ONLY, REGION_XN);
    mpu_set_region_limit(mpu, 0x30001FFFUL, 1, REGION_EN);
    mpu_set_region_attr(mpu, 0, 1); /*device memory*/
	//打开MPU
    mpu_hfnmiena_disable(mpu);
    mpu_privdefena_enable(mpu);
    mpu_enable(mpu);
    easy_printf("%s:mpu setup done\n", __func__);
	//打开MPU后,由于0x30001000的地址在region0和region1中重叠了,访问该地址会触发hard fault
	//代码执行到这一句的时候会触发异常
    easy_printf("0x30001000:%x\n", *temp_addr);
    easy_printf("%s done\n", __func__);
}
qemu-system-arm: invalid accelerator kvm
qemu-system-arm: falling back to tcg
hello cortex m33
0x30001000:00000000
0x30001000:00000001
test_armv8m_mpu_overlap:mpu setup done
default_exception_handler entry

2.2.4 执行权限测试

void test_func_print()
{
    easy_printf("%s entry\n", __func__);
}

void test_armv8m_xn()
{
    /* Inject code at 0x30001000 */
    //在0x300001000地址注入代码test_func
typedef void (*test_func_t)(void);
    volatile uint32_t *temp_addr = (volatile uint32_t *)0x30001000UL;
    test_func_t test_f = (test_func_t )0x30001001;//Thumb指令集,所以函数call的时候地址要加1
    /*  1000041c :
        1000041c:   b500        push    {lr}
        1000041e:   4801        ldr r0, [pc, #4]    ; (10000424 )
        10000420:   4780        blx r0
        10000422:   bd00        pop {pc}
        10000424:   100005eb    andne   r0, r0, fp, ror #11 //100005eb是test_func_print的地址
    */
    *temp_addr++ = 0x4801b500;
    *temp_addr++ = 0xbd004780;
    *temp_addr++ = 0x100005eb;
    //使能MPU前可以执行在0x30001000处的test_func
    test_f();
    
    //使能MPU region0:0x30000000 - 0x300010000
    //只读,不可执行,device memory
    armv8m_mpu_t *mpu = (armv8m_mpu_t *)0xE000ED90;
    mpu_disable(mpu);
    mpu_select_region(mpu, 0);
    mpu_set_region_base(mpu, 0x30000100UL, REGION_NON_SHAREABLE, REGION_RO_PRIV_ONLY, REGION_XN);
    mpu_set_region_limit(mpu, 0x30001FFFUL, 0, REGION_EN);
    mpu_set_region_attr(mpu, 0, 0); /*device memory*/
    mpu_hfnmiena_disable(mpu);
    mpu_privdefena_enable(mpu);
    mpu_enable(mpu);
    easy_printf("%s:mpu setup done\n", __func__);
    //使能MPU后不可执行0x30001000的test_func,因此代码执行到这里的时候会触发hardfault
    test_f();
}   

log

qemu-system-arm: invalid accelerator kvm
qemu-system-arm: falling back to tcg
hello cortex m33
test_func_print entry
test_armv8m_xn:mpu setup done
default_exception_handler entry

你可能感兴趣的:(ARM,嵌入式开发,C)