ARMv7-A小系统实践(十):L1、L2、屏障与Cache一致性

本章记录什么

这一章解释 L1 I/D Cache、分支预测、SCU 和 L2C-310 的启用与维护。核心问题不是“把 SCTLR 的某一位置 1”,而是旧缓存状态何时失效、页表何时需要 clean、跨核共享何时依赖 SCU,以及 DMA 或自修改代码出现后软件应怎样维护一致性。

在这一章之前做了什么

上一章已经为普通内存和 MMIO 建立了不同的页表属性。只有在属性正确后,Cache 的行为才有可推导基础。启动阶段还要先无效化旧状态,再按顺序启用 SCU、ACTLR.SMP、L1 和 L2;运行阶段则要区分 clean、invalidate 和 clean+invalidate。

本章从概念、顺序和屏障开始,随后给出完整的 Cache 实现代码。最后会明确当前小系统证明了哪些 CPU 一致性能力,以及尚未通过 DMA 场景证明什么。

系列目录 · 上一篇:短描述符页表与 MMU

本章的阅读顺序

先读基础概念,确认每个术语解决什么问题;再看设计推导,理解实现为什么采用这种保存集合、初始化顺序或状态机;随后逐段阅读代码和执行轨迹;最后用验证方法与错误案例检查理解。文中保留寄存器名、指令名和标准缩写,叙述部分统一使用简体中文。

从移植步骤理解 Cache 目标

十六、步骤 11:L1 Cache、SCU 与 L2C-310

先失效 L1 D/I 和 branch predictor,开启 SCU 与 ACTLR.SMP 后再开 Cache;L2C-310
也必须先 invalidate way、等待完成,再 enable。

1
2
3
4
开Cache后UART坏:查MMIO是否误标成Normal
CPU1看不到release:查Shareable、SCU、clean、DSB和SEV
修改代码仍执行旧指令:查D clean、I invalidate和ISB
DMA数据旧:查所有权和range维护(本原型尚无DMA实测)

验证 SCTLR.C/I/Z、SCU control、ACTLR.SMP 和 L2 control/ID,并重新运行 UART、IRQ、
任务切换与 CPU1 发布测试。打开控制位只是配置证据,既有路径继续工作才是行为证据。

页表属性与 Cache 初始化的连接

4. MMU 与 Cache 设计

当前采用 identity mapping,虚拟地址等于物理地址;这不代表“没有 MMU”,而是便于
早期移植调试,同时已落实权限和内存类型。

区域 属性
DDR 大区 Normal、WBWA、Shareable
镜像 .vectors/.text/.rodata 只读、可执行
.data/.bss/.stacks/.translation_tables 可读写、XN
UART/SLCR/SCU/GIC/Timer/L2C-310 Device、Shareable、XN
未显式映射区域 Fault descriptor

初始化顺序不能随意调换:

1
2
3
4
5
6
7
建立并 clean 页表
→ TLB invalidate
→ TTBR0/TTBCR/DACR
→ SCTLR.M
→ SCU + ACTLR.SMP
→ L1 invalidate 后开 C/I/Z
→ L2C-310 invalidate 后 enable

DMA 尚未接入。真板增加 DMA 后,必须根据缓冲区是否 cacheable 设计
clean/invalidate、屏障和所有权交接,不能只因为 CPU 读写正常就认为一致性完成。

最终审计中的 Cache 验收项

C. Cache 与一致性(必做 L1,条件验证 L2)

  1. 读取 SCTLR/CLIDR/CCSIDR
  2. 开启前按 set/way 失效 L1 D-Cache;
  3. 失效 I-Cache 和 branch predictor;
  4. DSB/ISB 串联维护操作;
  5. 设置 SCTLR.C/I/Z,验证读回位;
  6. 提供 range clean/invalidate 和 instruction sync 最小 API;
  7. SMP 时设置 ACTLR.SMP 并先启用 SCU;
  8. 检测并初始化 Zynq L2C-310。若 QEMU 模型不提供有效 L2 寄存器,输出明确 SKIP
    不伪造成功;真板流程和寄存器验证写入 README。

L1、SCU、ACTLR.SMP 与 L2C-310 完整实现

下面给出文章对应的完整实现片段。即使没有配套仓库,也可以沿着注释、寄存器访问和调用关系逐行阅读。代码之后的分析只讨论本章主题,不要求预先掌握其他目录结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#include <stddef.h>
#include <stdint.h>

#include "cache.h"
#include "platform.h"

enum {
SCU_CONTROL_ADDRESS = ZYNQ_A9_PRIVATE_PERIPHERAL_BASE + 0x0000u,
L2C_BASE = 0xf8f02000u,
L2C_CACHE_ID = 0x000u,
L2C_CONTROL = 0x100u,
L2C_CACHE_SYNC = 0x730u,
L2C_INVALIDATE_WAY = 0x77cu,

SCTLR_C = 1u << 2,
SCTLR_Z = 1u << 11,
SCTLR_I = 1u << 12,
ACTLR_SMP = 1u << 6
};

static uint32_t detected_l2_id;
static uint32_t l2_enabled;

static volatile uint32_t *register32(uint32_t address)
{
return (volatile uint32_t *)(uintptr_t)address;
}

static uint32_t count_leading_zeroes(uint32_t value)
{
uint32_t result;
__asm__ volatile("clz %0, %1" : "=r"(result) : "r"(value));
return result;
}

static void select_cache(uint32_t csselr)
{
__asm__ volatile("mcr p15, 2, %0, c0, c0, 0" :: "r"(csselr) : "memory");
arm_instruction_sync_barrier();
}

static uint32_t read_ccsidr(void)
{
uint32_t value;
__asm__ volatile("mrc p15, 1, %0, c0, c0, 0" : "=r"(value));
return value;
}

static void invalidate_l1_data_cache(void)
{
uint32_t clidr;
__asm__ volatile("mrc p15, 1, %0, c0, c0, 1" : "=r"(clidr));

for (uint32_t level = 0u; level < 7u; ++level) {
const uint32_t cache_type = (clidr >> (level * 3u)) & 0x7u;
if (cache_type < 2u || cache_type > 4u) {
continue;
}

select_cache(level << 1);
const uint32_t ccsidr = read_ccsidr();
const uint32_t line_shift = (ccsidr & 0x7u) + 4u;
const uint32_t ways = ((ccsidr >> 3) & 0x3ffu) + 1u;
const uint32_t sets = ((ccsidr >> 13) & 0x7fffu) + 1u;
const uint32_t way_shift = ways > 1u ? count_leading_zeroes(ways - 1u) : 0u;

for (uint32_t way = ways; way-- > 0u;) {
for (uint32_t set = sets; set-- > 0u;) {
const uint32_t operand = (level << 1) |
(way_shift != 0u ? way << way_shift : 0u) |
(set << line_shift);
__asm__ volatile("mcr p15, 0, %0, c7, c6, 2" :: "r"(operand) : "memory");
}
}
}

select_cache(0u);
arm_data_sync_barrier();
}

static void initialize_l2(void)
{
detected_l2_id = *register32(L2C_BASE + L2C_CACHE_ID);
if (detected_l2_id == 0u || detected_l2_id == 0xffffffffu) {
l2_enabled = 0u;
return;
}

if ((*register32(L2C_BASE + L2C_CONTROL) & 1u) == 0u) {
*register32(L2C_BASE + L2C_INVALIDATE_WAY) = 0xffu;
while ((*register32(L2C_BASE + L2C_INVALIDATE_WAY) & 0xffu) != 0u) {
}
*register32(L2C_BASE + L2C_CACHE_SYNC) = 0u;
*register32(L2C_BASE + L2C_CONTROL) = 1u;
arm_data_sync_barrier();
}
l2_enabled = *register32(L2C_BASE + L2C_CONTROL) & 1u;
}

void zynq_cache_initialize(void)
{
/* SCU 和 ACTLR.SMP 必须先于共享 Normal memory 的 D-Cache 使用。 */
*register32(SCU_CONTROL_ADDRESS) |= 1u;
uint32_t actlr;
__asm__ volatile("mrc p15, 0, %0, c1, c0, 1" : "=r"(actlr));
actlr |= ACTLR_SMP;
__asm__ volatile("mcr p15, 0, %0, c1, c0, 1" :: "r"(actlr) : "memory");
arm_data_sync_barrier();
arm_instruction_sync_barrier();

invalidate_l1_data_cache();
__asm__ volatile("mcr p15, 0, %0, c7, c5, 0" :: "r"(0u) : "memory");
__asm__ volatile("mcr p15, 0, %0, c7, c5, 6" :: "r"(0u) : "memory");
arm_data_sync_barrier();
arm_instruction_sync_barrier();

initialize_l2();

uint32_t control = arm_read_sctlr();
control |= SCTLR_C | SCTLR_I | SCTLR_Z;
__asm__ volatile("mcr p15, 0, %0, c1, c0, 0" :: "r"(control) : "memory");
arm_instruction_sync_barrier();
}

void zynq_cache_initialize_secondary(void)
{
uint32_t actlr;
__asm__ volatile("mrc p15, 0, %0, c1, c0, 1" : "=r"(actlr));
actlr |= ACTLR_SMP;
__asm__ volatile("mcr p15, 0, %0, c1, c0, 1" :: "r"(actlr) : "memory");
arm_data_sync_barrier();
arm_instruction_sync_barrier();

invalidate_l1_data_cache();
__asm__ volatile("mcr p15, 0, %0, c7, c5, 0" :: "r"(0u) : "memory");
__asm__ volatile("mcr p15, 0, %0, c7, c5, 6" :: "r"(0u) : "memory");
arm_data_sync_barrier();
arm_instruction_sync_barrier();

uint32_t control = arm_read_sctlr();
control |= SCTLR_C | SCTLR_I | SCTLR_Z;
__asm__ volatile("mcr p15, 0, %0, c1, c0, 0" :: "r"(control) : "memory");
arm_instruction_sync_barrier();
}

void zynq_cache_clean_range(const void *address, size_t size)
{
uintptr_t current = (uintptr_t)address & ~(uintptr_t)31u;
const uintptr_t end = (uintptr_t)address + size;
while (current < end) {
__asm__ volatile("mcr p15, 0, %0, c7, c10, 1" :: "r"(current) : "memory");
current += 32u;
}
arm_data_sync_barrier();
}

void zynq_cache_invalidate_range(const void *address, size_t size)
{
uintptr_t current = (uintptr_t)address & ~(uintptr_t)31u;
const uintptr_t end = (uintptr_t)address + size;
while (current < end) {
__asm__ volatile("mcr p15, 0, %0, c7, c6, 1" :: "r"(current) : "memory");
current += 32u;
}
arm_data_sync_barrier();
}

uint32_t zynq_cache_l1_is_enabled(void)
{
const uint32_t required = SCTLR_C | SCTLR_I;
return (arm_read_sctlr() & required) == required;
}

uint32_t zynq_cache_l2_is_enabled(void)
{
return l2_enabled;
}

uint32_t zynq_cache_l2_id(void)
{
return detected_l2_id;
}

uint32_t zynq_scu_is_enabled(void)
{
return *register32(SCU_CONTROL_ADDRESS) & 1u;
}

从规则走到实际实现

下面回到本章对应的小系统实现。这里不再假定读者已经打开源码,而是把关键地址、数据结构、指令序列和返回路径直接放在文章中解释。

把地址转换和缓存分开

MMU 负责翻译与访问属性,Cache 负责保存近期数据或指令副本。TLB 缓存翻译结果,D-Cache 缓存数据,I-Cache 缓存指令,它们的失效操作不能互相替代。

Zynq-7020 的基本关系可画成:

1
2
3
4
5
CPU0 ─ L1 I/D ─┐
├─ SCU/共享互连 ─ L2C-310 ─ DDR
CPU1 ─ L1 I/D ─┘

DMA 与其他总线主设备的路径另行确认

页表把 DDR 标记为 Normal WBWA/Shareable,使它具有可缓存与共享的语义;但还必须启用相应 CPU Cache、一致性机制并满足互连要求。S 位本身不会替软件完成所有同步。

clean、invalidate、clean+invalidate

操作 对脏数据的处理 适用思路
clean 把修改内容推向该操作定义的目标可见点,通常仍保留副本 让其他观察者有机会看到 CPU 写入
invalidate 丢弃已有副本;脏内容可能丢失 CPU 后续必须重新获取更新内容
clean+invalidate 先写回,再丢弃 需要保留修改又去除旧副本的场景

“写回”不能不加限定地等同于“立即写到 DDR 芯片”。系统可能还有外层 Cache、不同一致性点和旁路设备访问。要明确操作到 PoU、PoC,或外部控制器维护的哪一层。

盲目 invalidate 一个含有尚未写回数据的行,可能把最近修改直接丢掉。早期初始化可以 invalidate 的前提,是不存在需要保留的有效脏数据;从已经运行的 Bootloader 接管时,不能默认这一条件成立。

基础版开启 Cache 的实际顺序

src/cache.c 为准,CPU0 依次做:

1
2
3
4
5
6
7
8
9
设置 SCU enable
设置本核 ACTLR.SMP
DSB / ISB
按 set/way invalidate 本核数据 Cache
invalidate I-Cache 和分支预测状态
DSB / ISB
检查并初始化 L2C-310
设置 SCTLR.C / I / Z
ISB

这里 L2 初始化在最后设置 L1 开关之前。读代码时要按真实调用顺序,不用一段概括性 README 替代源文件。

三个 SCTLR 位:

1
2
3
C:数据 Cache
I:指令 Cache
Z:分支预测

ACTLR.SMP 是 Cortex-A9 一致性配置中的重要条件,SCU 是共享组件。将两个核的共享 Normal memory 配置为 shareable,还要让每核采用相容的 Cache 与 ACTLR 设置。

CPU1 路径不会再次初始化共享 L2,而是设置自己的 ACTLR、清理本核 L1 并启用本核 C/I/Z。每个核的 L1 状态不能由 CPU0 的一条 CP15 写入代劳。

为什么通过 CLIDR、CCSIDR 遍历

直接写死“Cache 有多少组、多少路”虽然短,但会把实现参数埋进代码。工程先通过 CLIDR 判断各级 Cache 类型,再选择 CSSELR,读取 CCSIDR:

1
2
3
const uint32_t line_shift = (ccsidr & 0x7u) + 4u;
const uint32_t ways = ((ccsidr >> 3) & 0x3ffu) + 1u;
const uint32_t sets = ((ccsidr >> 13) & 0x7fffu) + 1u;

随后按 way/set 组合 operand,执行对应的 D-Cache invalidate by set/way。

这段遍历针对 CP15 可见的缓存结构。外部 PL310 通过自己的 MMIO 接口控制,不能以为 CLIDR 遍历自动覆盖了 SoC 中所有外层缓存。

代码中的 way 编码还使用 CLZ 计算位置。读这种汇编操作数时,要对照 Cache 几何与寄存器编码,不是简单把 way 与 set 数字相加。

PL310 初始化做到了什么

工程读取 L2C-310 的 ID,排除全零或全一等明显无效值;若尚未启用,则写 invalidate-by-way,等待位清零,再执行 cache sync 并打开 controller。

1
2
3
4
5
*reg(L2C_INVALIDATE_WAY) = 0xffu;
while ((*reg(L2C_INVALIDATE_WAY) & 0xffu) != 0u) {
}
*reg(L2C_CACHE_SYNC) = 0u;
*reg(L2C_CONTROL) = 1u;

这里的 0xff 依赖当前目标配置的路数假设。真板还涉及辅助控制、RAM latency、预取配置与芯片勘误,不能把一个 controller enable 位当成通用最佳配置。

QEMU 输出 L2C-310 enabled 证明模型接受了这条控制路径;它不是 Cache hit ratio、真实 DDR 带宽或非一致 DMA 正确性的测量。

三种屏障各解决什么

指令 主要约束 典型位置
DMB 对指定域内的相关访存建立顺序 发布数据与标志、锁的 acquire/release 实现
DSB 等待相应操作完成后再继续 Cache/TLB 维护、设备清源与后续动作
ISB 后续指令按更新后的执行上下文重新取用 改 MMU/系统控制状态、部分指令同步流程

DMB 不是“清空 Cache”,DSB 也不会自动替每一条脏 Cache line 发出 clean 操作。ISB 不是数据同步指令,不能靠它发布另一核读取的数据。

屏障还有作用域和访问类别,生产代码可以用恰当范围降低开销。早期裸机代码使用较强的默认屏障便于建立清晰顺序,但不应由此推导“所有位置放 DSB 都正确且性能最好”。

volatile、memory clobber 和硬件屏障

这三个层次要分开:

1
2
static volatile uint32_t flag;
__asm__ volatile("dmb" ::: "memory");

volatile 约束编译器对对象访问的处理。"memory" clobber 告诉编译器这个汇编与内存状态有关,限制它把相关访问跨过这个点重排。DMB 指令本身作用于 CPU 的内存顺序。

只有 volatile,没有同步协议,不能消除多核数据竞争;只有 asm volatile,也不自动等价于 compiler memory barrier;有硬件 DMB,也不代表一个未经定义的 C 并发访问就成为可移植正确程序。

小系统用 volatile 配合特定硬件操作形成实验性握手。扩展成通用内核数据结构时,应明确使用原子操作或受锁保护的访问,并同时约束编译器和硬件。

range 维护为何按 Cache line 对齐

当前 A9 代码按 32 字节行处理地址范围:

1
2
3
4
5
6
7
8
uintptr_t current = (uintptr_t)address & ~(uintptr_t)31u;
const uintptr_t end = (uintptr_t)address + size;
while (current < end) {
__asm__ volatile("mcr p15, 0, %0, c7, c10, 1"
:: "r"(current) : "memory");
current += 32u;
}
arm_data_sync_barrier();

开始地址向下取整,是为了覆盖首个不完整行;循环直到覆盖结束位置。它展示了按地址 clean 的基本写法,但还不是通用 DMA 缓冲区 API。

首先,首尾行可能同时含有无关对象。invalidate 整行可能影响邻居的脏数据;解决办法包括让缓冲区独占完整 Cache line、正确处理首尾行以及定义明确的所有权。

其次,当前函数没有专门处理零长度和 address+size 溢出等参数情况。通用化时需要补齐,不能因为主调用点只传一个正常变量就认为接口已经覆盖全部输入。

再次,这里只出现 CP15 range 操作,没有单独实现 PL310 的 DMA 外层维护协议。它在具体系统中是否足以让旁路设备看到数据,必须按互连路径、PoC 和 controller 要求确认。

两个 CPU 一致,不代表 DMA 一定一致

CPU0 与 CPU1 都加入 A9 的一致性域后,共享缓存行可以通过一致性机制保持相容。但一个通过非一致端口访问 DDR 的 DMA,并不必然参与这套协议。

发送方向的职责通常是:

1
2
3
4
5
CPU 填充缓冲区
→ 按平台要求 clean 到 DMA 可见位置
→ 完成相应屏障
→ 发布描述符/启动 DMA
→ DMA 完成后才能重新修改仍由设备持有的区域

接收方向则要防止 CPU 持有旧副本,或者把旧脏行在 DMA 写完后又写回复盖新数据:

1
2
3
4
5
6
准备接收缓冲区与所有权
→ 必要的 DMA 前维护
→ 启动设备
→ 确认完成,按平台要求同步
→ invalidate CPU 旧副本
→ CPU 读取结果

具体前置操作可能随平台而异,不能简化为“发送永远只 clean,接收永远只在最后 invalidate”。ACP 与非一致 AXI 路径的要求也不同。当前工程没有 DMA 驱动,所以这些是设计约束,不是已验收功能。

修改代码时还要同步 I-Cache

CPU 通过 D-Cache 写出新指令,并不意味着 I-Cache 会自动使用新内容。动态代码、加载模块或修改向量指令时,通常需要适当的 D-side clean、I-side invalidate,以及 DSB/ISB 顺序。

当前程序链接后直接执行固定代码,没有自修改代码流程。但如果将来把“内存里看起来已经改了”直接当作“下一次必定执行新指令”,会遇到另一类 Cache 错误。

发生疑似 Cache 问题时记录什么

先列出访问者和路径,再查属性:

1
2
3
4
5
6
7
谁写:CPU0 / CPU1 / DMA
谁读:CPU0 / CPU1 / DMA
VA/PA:是否指向同一物理位置
属性:Normal/Device、cacheable、shareable 是否一致
所有权:何时允许另一方访问
维护:对哪一级、哪一段地址、哪个可见点
顺序:发布标志是否晚于有效数据

把 Cache 临时关闭能帮助定位,但“关闭后正常”不等于最终应该关闭 Cache。需要继续找到是哪一个属性、维护或交接条件不成立。

本篇对应源码为两份小系统的 src/cache.c;架构语义查 ARM ARM 的 memory model/cache maintenance,A9 与 PL310 的实现细节查对应 TRM 和 UG585 APU 章节。

下一篇:CPU1、SCU 与 SGI

从一次真实修正理解 Cache 调试

Cache 问题最危险的地方,是症状经常落在别的模块:CPU1 看不到发布标志、页表更新似乎无效,或者关闭优化后故障消失。下面的记录把实现顺序与一次真实校正放在一起,强调必须区分架构规则、QEMU 行为和真板证据。

8.2 MMU 与 Cache

新增 16 KiB L1 translation table 和 1 KiB L2 coarse table。采用 identity map,
但落实了权限与类型:

  • 镜像代码只读、可执行;
  • data/bss/stack/page table 可写、XN;
  • DDR 为 Normal WBWA/Shareable;
  • UART、SLCR、private peripherals 与 L2C-310 为 Device/Shareable/XN;
  • 0x70000000 保持 unmapped,PAR 查询应报告 fault。

CPU0 依次启用 MMU、SCU、ACTLR.SMP、L1 I/D Cache 与 L2C-310;CPU1 使用同一页表,
但独立设置 TTBR/TTBCR/DACR/SCTLR、L1 和 ACTLR.SMP。QEMU 实测:

1
2
3
SCTLR=0x00c5187d TTBR0=0x00114000 image_pa=0x00100000 L2_ID=0x410000c8
PASS: MMU, protected memory attributes, L1 caches and SCU are enabled
PASS: L2C-310 enabled

Cache 与一致性相关的平台寄存器

SCU、GIC、Private Timer 和 L2C-310 都位于固定物理地址。把常量集中起来,能在调试时快速确认操作对象是否正确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#ifndef ZYNQ7020_PLATFORM_H
#define ZYNQ7020_PLATFORM_H

#include <stdint.h>

#define ZYNQ_UART0_BASE 0xE0000000u
#define ZYNQ_A9_PRIVATE_PERIPHERAL_BASE 0xF8F00000u

static inline uint32_t arm_read_cpsr(void)
{
uint32_t value;
__asm__ volatile("mrs %0, cpsr" : "=r"(value));
return value;
}

static inline uint32_t arm_read_mpidr(void)
{
uint32_t value;
__asm__ volatile("mrc p15, 0, %0, c0, c0, 5" : "=r"(value));
return value;
}

static inline uint32_t arm_cpu_index(void)
{
return arm_read_mpidr() & 0x3u;
}

static inline uint32_t arm_read_sctlr(void)
{
uint32_t value;
__asm__ volatile("mrc p15, 0, %0, c1, c0, 0" : "=r"(value));
return value;
}

static inline uint32_t arm_read_dfsr(void)
{
uint32_t value;
__asm__ volatile("mrc p15, 0, %0, c5, c0, 0" : "=r"(value));
return value;
}

static inline uint32_t arm_read_ifsr(void)
{
uint32_t value;
__asm__ volatile("mrc p15, 0, %0, c5, c0, 1" : "=r"(value));
return value;
}

static inline uint32_t arm_read_dfar(void)
{
uint32_t value;
__asm__ volatile("mrc p15, 0, %0, c6, c0, 0" : "=r"(value));
return value;
}

static inline uint32_t arm_read_ifar(void)
{
uint32_t value;
__asm__ volatile("mrc p15, 0, %0, c6, c0, 2" : "=r"(value));
return value;
}

/* 读取当前模式实际可见的 SP;在 IRQ C 处理函数中读到的就是 sp_irq。 */
static inline uintptr_t arm_read_sp(void)
{
uintptr_t value;
__asm__ volatile("mov %0, sp" : "=r"(value));
return value;
}

static inline void arm_wait_for_interrupt(void)
{
__asm__ volatile("wfi" ::: "memory");
}

static inline void arm_wait_for_event(void)
{
__asm__ volatile("wfe" ::: "memory");
}

static inline void arm_send_event(void)
{
__asm__ volatile("sev" ::: "memory");
}

static inline void arm_enable_irq(void)
{
__asm__ volatile("cpsie i" ::: "memory");
}

static inline void arm_disable_irq(void)
{
__asm__ volatile("cpsid i" ::: "memory");
}

static inline void arm_data_sync_barrier(void)
{
__asm__ volatile("dsb" ::: "memory");
}

static inline void arm_instruction_sync_barrier(void)
{
__asm__ volatile("isb" ::: "memory");
}

#endif

Cache 维护接口

接口刻意区分全局启用和按范围维护。以后接入 DMA 时,应在所有权交接点调用范围操作,而不是随意清空整个 Cache。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef ZYNQ7020_CACHE_H
#define ZYNQ7020_CACHE_H

#include <stddef.h>
#include <stdint.h>

void zynq_cache_initialize(void);
void zynq_cache_initialize_secondary(void);
void zynq_cache_clean_range(const void *address, size_t size);
void zynq_cache_invalidate_range(const void *address, size_t size);
uint32_t zynq_cache_l1_is_enabled(void);
uint32_t zynq_cache_l2_is_enabled(void);
uint32_t zynq_cache_l2_id(void);
uint32_t zynq_scu_is_enabled(void);

#endif

重点回看

Cache 正确性依赖页表属性、维护操作、屏障和所有权协议。clean 负责写回,invalidate 负责丢弃,二者不可混用。SCU 解决 Cortex-A9 核间一致性的一部分,但不能自动替代 DMA 的软件同步,也不能替代自修改代码所需的 I/D Cache 协调。

本章总结

Cache 正确性依赖页表属性、维护操作、屏障和所有权协议。clean 负责写回,invalidate 负责丢弃,二者不可混用。SCU 解决 Cortex-A9 核间一致性的一部分,但不能自动替代 DMA 的软件同步,也不能替代自修改代码所需的 I/D Cache 协调。

下一章释放 CPU1,建立每核栈、每核 GICC 与 SGI 往返验证。

ARMv7-A小系统实践(十):L1、L2、屏障与Cache一致性

https://goko-son626.github.io/post/armv7a-09-cache.html

作者

GoKo Mell

发布于

2026-07-04

更新于

2026-07-04

许可协议

评论

:D 一言句子获取中...