一次 AMP IPC 挂死的三层排查:缓存属性、中断原子性与协议标志位
0. 背景
rt-async-amp 的 K3 真板联调收尾阶段,AP(X100 大核,StarryOS,S-mode)与 RP(RT24 rcpu1,rt-async,M-mode)经 RCPU SRAM 共享窗(0xc0800000, 0x19000)上的 ov-channels SharedMemory<3> 通信:ch0 = AP→RP 请求,ch1 = RP→AP 回包;通知面用 mailbox4 硬件门铃代替轮询——AP 侧经 APLIC(MSI 模式, source 217)→ IMSIC 投递,RP 侧是 PLIC IRQ 69。AP 用户态经 /dev/rt_shm 的 ioctl NOTIFY(发完消息打门铃)/AWAIT(停靠等回包门铃)收发。
验收程序 user-test-ipc 每轮做两件事:发一条 notification 等 RP 回显,再发 一条 ADD RPC 请求等响应,共三轮。
症状出现在最后一步:
./user-test-ipc # 裸跑:卡死在 round-1 的 ADD AWAIT
wget 192.168.0.121:8000/user-test-ipc -O ... && ./user-test-ipc # 每次都三轮全过notification 回显永远正常,挂的只有 ADD。更迷惑的是:先起一个 shell 忙等 任务再裸跑,照样挂——排除了"CPU 休眠/WFI 拖慢唤醒"的假说。
最终定案:这不是一个 bug,是叠在一起的三层 bug,每层都真实存在、都能 独立造成"AWAIT 挂死"这个同一症状。本文按剥洋葱的顺序复盘。
1. 第一层:X100 上 PBMT 根本不生效(缓存属性)
错误假设:AP 侧把共享窗 mmap 成 PTE PBMT=NC(Svpbmt 扩展),以为用户态 读写直达 SRAM,天然跨核一致。
板上实锤:一切"NC 映射"实际都在走 dcache。X100 的 PMA 按地址判定—— mailbox 等真 MMIO 天然非缓存(所以中断路径一直"正常"),而共享 SRAM 是真 RAM,PMA=cacheable,PTE 写 PBMT=NC 压不住(疑似 OpenSBI 未置 menvcfg.PBMTE,被静默忽略且不报错)。
修复:不再信任任何"NC 映射"语义,一致性全部改为显式 CBO(zicbom dcache_range),在 rt_shm 驱动里布了四个同步点:
| 时机 | 操作 | 目的 |
|---|---|---|
| 设备初始化 | invalidate | 丢弃启动链(SPL/U-Boot cacheable 写)遗留脏行 |
| mmap | invalidate | 清内核早期访问驻留行,用户态首写直达 SRAM |
| NOTIFY(门铃前) | clean+invalidate | 把本核滞留写推到 SRAM 再通知对端 |
| AWAIT(每次就绪检查前 + 返回前) | invalidate / clean+invalidate | 读到对端已写入 SRAM 的真值 |
其中 AWAIT 有个隐蔽细节:poll 闭包首查与注册 waker 之间的锁内重查,必须 各自先 invalidate 一次——首查会把"空"快照取入缓存行,若对端回包恰落在 首查与注册之间,锁内重查会命中首查的陈旧行而误判"无数据",随后注册、 永久挂死。首查与重查之间只隔几条指令,但这段窗口真实存在。
这一层修复后,早期的 60/120/180s ping 挂死消失了——但 ADD 裸跑挂死依旧, 且挂点还会随打印多少漂移。说明下面还有。
2. 第二层:stopei 读写分离吞 MSI(中断原子性)
AP 的门铃走 IMSIC(MSI 中断控制器)。RISC-V AIA 的 stopei 寄存器语义:
- 读:返回当前最高优先级 pending 中断的(eiid, priority),非破坏;
- 写(任意值):complete 写入时刻的最高优先级 pending,将其清掉。
原实现把 claim 和 complete 拆成两条指令:进入 handler 前读 stopei,handler 返回时(Drop)再写一次。两条指令之间若有新 MSI 落到更高优先级位,写的清 掉的是"写入时刻"的新中断而不是刚才处理那个——新门铃被吞。APLIC 的电平 源(mailbox NEW_MSG)只在 setip 0→1 跳变投一次 MSI,吞一次 = 中断线永久挂 高,后续门铃全部隐形。
这段弯路的起点是我记错了语义——以为"读即 deactivate",于是做了一个 "QEMU 上跳过补写"的门控 feature。上板把门控关掉,系统直接死在 eth0 DHCP 的第一个外部 MSI 上,门控假说当场证伪。回头查权威实现,三处一致:
- Linux(
drivers/irqchip/irq-riscv-imsic-early.c,imsic_handle_irq):csr_swap(CSR_TOPEI, 0)——单条csrrw,读写原子,写在 handler 之前; - QEMU(
hw/intc/riscv_imsic.c,riscv_imsic_topei_rmw):“Writes ignore value and clear top pending interrupt"; - 规范原文同上。
修复:claim 与 complete 合并为单条 csrrw stopei, x0,且前移到 handler 之前(tgoskits feat/riscv-aia,两个 commit)。门控 feature 整体删除。
这一层是独立的真 bug(不修它,任何 MSI 都可能随机丢)——但修完后 ADD 裸跑 挂死依旧。irq 计数证明门铃一枚不少地到达了 AP——那么问题只剩一种可能: 门铃根本没发。
3. 第三层:NOTIFY_FLAG 缺失——Quiet 回包没有门铃(真凶)
决定性证据是一对日志:
- RP 侧:
ADD(0,3) 处理完成 … total 1——请求收到、处理了、回包写进 ch1; - AP 侧:mailbox IRQ 计数停在 2——本轮门铃没来。
数据到了、中断没到、而中断只有 RP 会发 → 查协议。ov-rpc 约定 method_id 的 bit63 = NOTIFY_FLAG:
- 带标志:服务端回包后走
HandledKind::Notify→on_notify()→ 发 IPI 门铃; - 不带标志:
HandledKind::Quiet→ 回包进 ch1,不发任何通知。
user-test-ipc 手写 Message::request(rid, 1, &(a, b))——method_id 是裸的 1,没带标志。而 ov-rpc 自带的 RpcClient::call() 会自动置位,所以 RP 侧 自己的回显测试一直正常;notification 回显走的又是另一条无条件发门铃的路径 (on_other → send_message()),所以那半边也一直正常。三条"正常"的路把 唯一一条错的路围在中间,掩护到最后。
修复一行:let method = 1u64 | ov_rpc::NOTIFY_FLAG;。冷启动裸跑多次,三轮 全过。
wget 为什么能治愈
无门铃时,AWAIT 能否通过是一场 µs 级赛跑:
- AP 侧:NOTIFY 返回 → 进入 AWAIT → invalidate → 首次就绪检查,几十 µs;
- RP 侧:门铃 → ISR → 唤醒 → 处理 → 回包落 SRAM,上百 µs。
回包赶在首查前落好 → AWAIT 首查即命中,直接返回(我叫它 poll1-luck); 赶不上 → AWAIT 注册 waker 永久停靠(不会再有任何门铃)。裸跑时 AP 稳赢 前半程——挂。
wget 跑完后系统里残留着 ms 级扰动:eth0 的残余中断流、371KB 新文件的 UFS 写回与块设备完成中断、exec 的脏页缺页等待。这些把 AP 从 NOTIFY 到首查 的路径拖慢一两个数量级,RP 回包总是先到——三轮全过。忙等实验为什么无效? 它只分走 CPU,不产生中断和 IO,翻不了十倍的数量级差距。
同一个机制,两个方向的掩蔽:环境扰动决定竞态胜负时,"通过"是不可信的。
4. 参考索引
| 参考源 | 位置 | 关键点 |
|---|---|---|
| Linux AIA early handler | drivers/irqchip/irq-riscv-imsic-early.c imsic_handle_irq | csr_swap(CSR_TOPEI, 0) 单条 csrrw,写前移 |
| QEMU IMSIC | hw/intc/riscv_imsic.c riscv_imsic_topei_rmw | 写忽略值、清 top pending |
| K3 SDK Linux 6.18 | ~/projects/k3-buildroot-sdk-1.0/bsp-src/linux-6.18 | 上述文件出处 |
| ov-rpc 协议 | 主仓 modules/ov-rpc/src/{lib,server,client}.rs | NOTIFY_FLAG = 1<<63;HandledKind::{Quiet,Notify,OneWay} |
| rt_shm 驱动设计文档 | tgoskits os/StarryOS/kernel/src/pseudofs/dev/rt_shm.rs 模块头 | 缓存一致性模型、四个 CBO 同步点 |
| K3 mailbox M-mode 侧 | 主仓 modules/chip-k3-rt24/src/mailbox.rs | NEW_MSG 电平语义、FIFO 排空模式 |
相关提交:tgoskits feat/riscv-aia(EOI 前移 + csrrw 合一)、 feat/rt-async-amp(rt_shm CBO 同步点 + ack 排空 + 窗口收回);主仓 master(共享窗 SRAM 落位 + 启动期防护;user-test-ipc NOTIFY_FLAG 修复)。
文档完成于 2026-08-16。基于 K3 COM260 开发板真板调试,三轮 RPC 全绿收束。