PRRR寄存器详解

ARM内存属性重映射机制的核心组件

PRRR是干啥的?

PRRR(Primary Region Remap Register,主区域重映射寄存器)是ARM架构中用于控制内存属性映射的关键系统寄存器。它主要负责将页表中定义的TEX[0]、C和B位转换为实际的内存类型和共享属性。

简单来说,PRRR就像一个"翻译官",它告诉内存管理系统如何解释页表中的内存属性设置,从而确定某块内存应该是设备内存还是普通内存,以及这块内存应该是共享的还是非共享的。

PRRR怎么工作?

PRRR通过位字段来控制8种可能的内存属性组合(由TEX[0], C, B三位组合而成)的映射方式:

31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0

PRRR主要包含三种类型的控制字段:

什么场景需要使用PRRR?

PRRR在以下场景中特别重要:

内存属性定制

当默认的内存属性映射不满足需求时,可以通过配置PRRR来定制内存属性的解释方式。

多核一致性

在多核系统中,PRRR可以配置内存的共享属性,确保缓存一致性机制正确工作。

安全隔离

在安全和非安全世界之间,PRRR可以配置不同的内存属性映射,增强系统安全性。

实际应用示例

在Linux内核中,内存属性通常通过MAIR寄存器(PRRR的AArch64等效寄存器)进行配置:

/* Linux内核中配置内存属性的示例 */
#define MAIR(attr, mt) ((attr) << ((mt) * 8))

void setup_mair(void) {
    u64 mair = MAIR(0x00, MT_DEVICE_nGnRnE) |    /* 设备内存(nGnRnE) */
               MAIR(0x04, MT_DEVICE_nGnRE) |     /* 设备内存(nGnRE) */
               MAIR(0xcc, MT_NORMAL_NC) |        /* 非缓存普通内存 */
               MAIR(0xff, MT_NORMAL);            /* 回写式缓存普通内存 */
    
    asm volatile("msr mair_el1, %0" : : "r" (mair));
}
                

在ATF(ARM Trusted Firmware)和OP-TEE中,PRRR/MAIR的配置对于安全世界和非安全世界的内存隔离至关重要:

/* OP-TEE中配置安全内存属性的示例 */
void configure_secure_memory(void) {
    /* 配置安全世界的内存属性 */
    write_mair_el3(MAIR_ATTR(MAIR_ATTR_DEVICE, 0) |
                   MAIR_ATTR(MAIR_ATTR_WB_NWA, 1) |
                   MAIR_ATTR(MAIR_ATTR_NC, 2));
    
    /* 配置非安全世界的内存属性(可能不同) */
    configure_ns_memory_attributes();
}
                

PRRR与MAIR的关系

PRRR是AArch32架构中的寄存器,而在AArch64架构中,等效的功能由MAIR_EL1(Memory Attribute Indirection Register)提供。

当使用AArch32执行模式时:

这种设计保持了向前兼容性,允许旧的AArch32代码继续运行,同时支持新的内存属性模型。

编程示例

下面是在AArch32模式下访问PRRR的示例代码:

/* 读取PRRR寄存器的值 */
uint32_t read_prrr(void) {
    uint32_t prrr_value;
    asm volatile (
        "mrc p15, 0, %0, c10, c2, 0" 
        : "=r" (prrr_value)
    );
    return prrr_value;
}

/* 写入PRRR寄存器 */
void write_prrr(uint32_t value) {
    asm volatile (
        "mcr p15, 0, %0, c10, c2, 0"
        : 
        : "r" (value)
    );
}

/* 配置TR字段示例 */
void configure_memory_types(void) {
    uint32_t prrr = read_prrr();
    
    /* 设置属性0为设备内存(nGnRE) */
    prrr = (prrr & ~(0x3 << 0)) | (0x1 << 0);
    
    /* 设置属性1为普通内存(回写式缓存) */
    prrr = (prrr & ~(0x3 << 2)) | (0x2 << 2);
    
    write_prrr(prrr);
}