【ARM Cache 及 MMU 系列文章 6.2 -- ARMv8v9 如何读取 Cache 内部数据并对其进行解析?】
Direct access to internal memory
在ARMv8架构中,缓存(Cache)是用来加速数据访问的关键组件,它利用了程序执行中的局部性原理来提高性能。缓存中的每一行(Cache Line)通常包含了两个主要部分:实际的数据和一个标记(Tag)。这个标记用来标识存储在缓存行中的数据在内存中的位置。
关于cache 中 set 和 way 的定义见文章 : 【ARM Cache 系列文章 1 – Cache基础概念学习】
ARMv9架构中,核心提供了一种机制,可以通过实现定义的系统寄存器读取L1缓存、L2缓存和 TLB 中的内容。当cache中据与系统内存数据之间的一致性出现问题时,可以使用这个机制来调查问题。直接访问 internal memory 只在EL3(异常级别3)中可用。在所有其他异常级别中执行这些指令会导致 未定义的指令异常 。
以 ARM A720为例,可以通过下表中的12个只读寄存器来读取 internal memory中的内容。
Register name | Description | Access encoding |
---|---|---|
IMP_ISIDE_DATA0_EL3 | Instruction Data register 0 | MRS , S3_6_C15_C0_0 |
IMP_ISIDE_DATA1_EL3 | Instruction Data register 1 | MRS , S3_6_C15_C0_1 |
IMP_ISIDE_DATA2_EL3 | Instruction Data register 2 | MRS , S3_6_C15_C0_2 |
IMP_DSIDE_DATA0_EL3 | L1D Data register 0 | MRS , S3_6_C15_C1_0 |
IMP_DSIDE_DATA1_EL3 | L1D Data register 1 | MRS , S3_6_C15_C1_1 |
IMP_DSIDE_DATA2_EL3 | L1D Data register 2 | MRS , S3_6_C15_C1_2 |
IMP_L2_DATA0_EL3 | L2 Data register 0 | MRS , S3_6_C15_C1_3 |
IMP_L2_DATA1_EL3 | L2 Data register 1 | MRS , S3_6_C15_C1_5 |
IMP_L2_DATA2_EL3 | L2 Data register 2 | MRS , S3_6_C15_C1_4 |
IMP_MMU_DATA0_EL3 | TLB Data register 0 | MRS , S3_6_C15_C0_3 |
IMP_MMU_DATA1_EL3 | TLB Data register 1 | MRS , S3_6_C15_C0_4 |
IMP_MMU_DATA2_EL3 | TLB Data register 2 | MRS , S3_6_C15_C0_5 |
通过配置 IMPLEMENTATION DEFINED RAMINDEX
寄存器来选择要访问的 internal memory。
指令如下 :
SYS #6, C15, C0, #0, <Xt>
<Xt>
为通用寄存器
注意:通过上面指令可以选择读取 Cache 的 TAG值和DATA的值。
L1 cache encodings
上文中通用寄存器的值需要按照下面表格中格式进行配置:
- 如果L1 指令cache 大小为 64KB, 那么读取它的TAG中的值需要按照下面表格进行:
- 如果L1 指令cache 大小为 32KB, 那么读取它的TAG中的值需要按照下面表格进行:
- 如果L1 指令cache 大小为 64KB, 那么读取它的 CacheLine 中的数据 需要按照下面表格进行:
- 如果L1 指令cache 大小为 32KB, 那么读取它的 CacheLine 中的数据 需要按照下面表格进行:
- 如果要读取TAG中的内容,对于64KB的L1 Cache, 如果虚拟地址域使用的bit范围超过bit13,或者对于32KB 的L1 Cache 其虚拟地址使用的bit 范围超过bit12,那么就需要按照下面表格来配置:
- 如果要读取Cache Line 中的内容,对于64KB的L1 Cache, 如果虚拟地址域使用的bit范围超过bit13,或者对于32KB 的L1 Cache 其虚拟地址使用的bit 范围超过bit12,那么就需要按照下面表格来配置:
L1 Cache Data 寄存器
在通过上节寄存器的配置选择好读取 TAG内容还是 Cacheline 之后,就可以来读取数据了,数据的读取是通过下面寄存器进行的:
前面三行是给L1 指令cache使用的,后面三行是给 L1 data cache 使用的。我们继续看下指令寄存器数据格式。
- 对于L1 指令cache TAG内容使用下表内容解析
-
对于L1 data cache TAG内容使用下表内容解析 :
-
对于L1 data cache cacheline 中的内容使用下表内容解析 :
对于A520 其读取指令与A720有所差异,如下:
下面以读取A520 L1 Data Cache 中的内容为例进行简单介绍如何读取。
Cache 数据读取代码实现
假设A520 L1 data cache 的 CacheLine 的大小为64bytes, Cache 有 4 路(way) 128组(set),由于组数比较多,这里只读去第0组,代码如下:
void a520_cache_read(void)
{
*(uint64_t *)(0x90000000) = 0x5a5a5a5a5a;
for (int way = 0; way < 4; way++) {
for (int offset = 0; offset < 8; offset ++) {
uint64_t val = cache_read(0, way, offset);
log_info("set %d, way %d, offset %d, val %lx\n", 0, way, offset, val);
}
}
}
L1 Data cache 的读取操作应该按照下面格式去配置
汇编代码实现,如下:
/*
* w0: set
* w1: way
* w2: offset
* /
cache_read:
LSL w0, w0, #6
LSL w1, w1, #30
LSL w2, w2, #3
orr w0, w0, w1
orr w0, w0, w2
sys #6,c15,c4,#0, x0
isb
mrs x0, S3_6_C15_C0_0
ret
bit[5:3]
一共2bits可以组成 2 2 =4 种选择, 由于cacheline 为64bytes 且每次只能读取8bytes,所以需要通过offset来决定读取哪8bytes。
读取完成后需要按照下面格式解析:
测试结果
可以看到第0组,第1路,offset 为0的cacheline 中存放的数据是写入的数据。