Skip to content

一次 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 写)遗留脏行
mmapinvalidate清内核早期访问驻留行,用户态首写直达 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.cimsic_handle_irq): csr_swap(CSR_TOPEI, 0)——单条 csrrw,读写原子,写在 handler 之前
  • QEMU(hw/intc/riscv_imsic.criscv_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_idbit63 = NOTIFY_FLAG

  • 带标志:服务端回包后走 HandledKind::Notifyon_notify() → 发 IPI 门铃;
  • 不带标志:HandledKind::Quiet → 回包进 ch1,不发任何通知

user-test-ipc 手写 Message::request(rid, 1, &(a, b))——method_id 是裸的 1,没带标志。而 ov-rpc 自带的 RpcClient::call() 会自动置位,所以 RP 侧 自己的回显测试一直正常;notification 回显走的又是另一条无条件发门铃的路径 (on_othersend_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 handlerdrivers/irqchip/irq-riscv-imsic-early.c imsic_handle_irqcsr_swap(CSR_TOPEI, 0) 单条 csrrw,写前移
QEMU IMSIChw/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}.rsNOTIFY_FLAG = 1<<63HandledKind::{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.rsNEW_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 全绿收束。