上篇结束时,模型能进 shell 了。但接下来接 SD 卡,卡了两天。
现象:访问停了,CPU 却在睡觉
SD 控制器接上去,内核启动到一半不动了。抓 QEMU 的寄存器访问日志,最后几笔是这样的:
[glue] rd8 off=0x204 [glue] wr8 off=0x204 val=0x40 [glue] wr8 off=0x204 val=0x0
之后就什么都没有了——不是继续访问别的寄存器,是彻底没有访问。
再看 CPU 停在哪。用 monitor 读寄存器:
(qemu) info registers R13=c0d01f78 R14=c098392c R15=c0110468 PSR=60000093
拿 System.map 反查:
PC=0xc0110468 -> cpu_v7_do_idle + 0x8 LR=0xc098392c -> default_idle_call + 0x20
CPU 在 idle。 不是死循环,是在睡觉。
这就矛盾了:如果是等某个中断,中断应该还在来;如果是死循环,CPU 不该 idle。
用最笨的办法验一下
不猜了,直接在 shell 里敲:
~ # sleep 3
卡死。三秒永远不到。
定时器根本不工作。 之前所有测试都没碰到需要 sleep 的路径,所以一直没暴露。
追到 GIC 接线
内核里跟时间有关的中断只有一处来源:ARM 架构定时器。它的中断线要从 CPU 连到 GIC,代码是我写的,但显然接错了。
QEMU 的 GIC 输入数组不是按中断号排的:
【07_04_GIC输入数组】
前面是 SPI,后面才是每个 CPU 的 PPI。所以 PPI 的输入线号是:
输入线 = (num_irq - 32) + INTID
num_irq 是 128,所以基址是 96。设备树里写的是 <1 13>、<1 14> 这种 PPI 号,换算成 INTID 是 29、30,输入线应该是 125 和 126。
我一开始接的是 29——那是 SPI 的索引,打不到 PPI 上。后来改成直接接 13,又撞上断言:
qemu-system-arm: ../hw/intc/arm_gic.c:406: gic_set_irq: Assertion `irq >= GIC_NR_SGIS' failed.
因为块内偏移 0 到 15 是 SGI 区,PPI 必须从 16 起。
改成 96 + 29 = 125 之后:
~ # sleep 3 ~ # echo SLEEP_OK SLEEP_OK
这个 bug 的影响面
它不止挡住 SD。
在此之前,模型里任何需要 sleep 的路径都是死路——只是之前的测试恰好都没碰到。串口 shell 能用,是因为它靠中断驱动,不走定时等待。
所以修完之后,不只是 SD 能往下走了,整个模型的时间语义才算成立。
【07_05_排查路径】
回头看,怎么才能更快找到
有两条经验。
第一,别只看"卡在哪",要看"卡的状态"。 寄存器访问停了,很容易判断成"死循环"或"崩了"。但 CPU 在 idle 这个事实,直接把"死循环"排除了,范围一下缩到"在等什么东西"。
第二,找一个最小可判定的实验。 sleep 3 这条命令,比读一万行驱动代码都快。它把"定时器是否工作"这个问题,变成了一个二值判断。
我要赚赏金
