很多小伙伴都知道,epycd8这个主板有个常见问题,就是bmc的固件会花。只需要更换soic-16封装的flash就会好。近日我在某鱼看到了一个epycd8+7532的板u只需要1300,症状就是BMC坏,感觉一看就是固件花了很容易修,所以我马上就下单了。
可惜买完收到货之后,发现即使换了新的flash,BMC依旧是无法开机,把我整不会了。于是接上BMC的debug ttl,观察上电后的反应。
提示:后文由AI续写
串口是 ttyS4,115200。上电之后 U-Boot 本身是正常的,能进提示符,flash 也认得出来,所以问题根本不在固件:
U-Boot 2013.07 (Nov 07 2019 - 19:22:25)
I2C: ready
DRAM: 210 MiB
Flash: Found SPI Chip Winbond W25Q256(0x1940) FAST READ, NORMAL WRITE
32 MiB
Net: ast_eth0, ast_eth1
DRAM ECC enabled
让它 autoboot,内核加载、解压都正常,一路跑到释放 initrd 那一步,直接 panic,然后 Rebooting in 1 seconds,进入无限重启。完整的一次是这样:
[ 0.630000] Freeing initrd memory: 19388K (c1001000 - c22f0000)
[ 0.640000] Unable to handle kernel paging request at virtual address c0f23644
[ 0.640000] pgd = c0004000
[ 0.640000] [c0f23644] *pgd=80e0041e(bad)
[ 0.640000] Internal error: Oops: 8000000d [#1] ARM
[ 0.640000] CPU: 0 PID: 153 Comm: kworker/u2:0 Not tainted 3.14.17-ami #1
[ 0.640000] PC is at 0xc0f23644
[ 0.640000] LR is at ret_from_fork+0x14/0x3c
[ 0.640000] pc : [<c0f23644>] lr : [<c0009338>] psr: a0000153
[ 0.640000] Backtrace: no frame pointer
[ 0.640000] Code: 00000000 00000000 00000000 00000000 (00000000)
[ 0.640000] ---[ end trace 685caa4da424f918 ]---
[ 0.640000] Kernel panic - not syncing: Fatal exception
[ 0.640000] Rebooting in 1 seconds..
一开始我以为是内核 bug 或者 flash 没刷干净,但让它这么循环撞了几次,对比日志就发现不对劲了:每一次崩的地址都不一样。上面这次 PC 是 c0f23644,下一次就变成了 c09d9324:
[ 0.640000] Unable to handle kernel paging request at virtual address c09d9324
[ 0.640000] [c09d9324] *pgd=8080041e(bad)
[ 0.640000] PC is at 0xc09d9324
[ 0.640000] LR is at __atomic_notifier_call_chain+0x20/0x28
[ 0.640000] Code: 00000000 00000000 00000000 00000000 (00000000)
[ 0.640000] Kernel panic - not syncing: Fatal exception
地址在乱跳,而且两次的 Code: 都是一整排 00000000,CPU 取回来的指令全是 0。要是固定的软件 bug,崩的位置应该是稳定的才对,现在地址随机加上取指全 0,那就是内存故障了。
这版 U-Boot 没有 mtest,我就自己写脚本填 pattern 进去再逐字读回来对比,扫下来发现 0x87100000 开始有一整块连续 30MB 是坏的,读出来固定是脏值,写也写不进去,坏区之外的内存全是好的。内核加载正好会用到这一段,所以每次都崩在这。
先绕开坏块把系统跑起来
思路很简单,用 mem= 让内核别碰那 30MB 坏区就行,注意 ARM 不认 x86 的 memmap=。但直接改 mem= 让它 autoboot 一直卡在 Starting kernel 之前静默崩,查了半天才发现,手动 bootm 得自己把 kernel 和 initrd 从 flash 拷进 RAM,而且 initrd 不能被重定位走。配方是这样:
cp.b 0x21560040 0x80100000 0x29ac58 # kernel flash → RAM
cp.b 0x20260000 0x81000000 0x12f0040 # initrd flash → RAM
setenv bootargs root=/dev/ram0 ro ip=none ramdisk_blocksize=4096 mem=113M console=ttyS4,115200 rootfstype=cramfs bigphysarea=6144 imagebooted=1
setenv initrd_high 0xffffffff # initrd 就地不重定位
bootm 0x80100000 0x81000000
这样 BMC 就完整起来了,web、SNMP、Component Manager 全起,稳稳跑在坏区前面的 113MB。这里有个坑,cp.b 拷 19MB 的 initrd 很慢,超时要给足(60s 以上),拷完轮询一下 magic 0x56190527 确认到位再 bootm,不然会报 “Wrong Ramdisk Image Format”。
210MB?这板明明是 512MB
系统起来我就注意到一件怪事,DRAM: 210 MiB,但板上是 K4A4G16,物理明明是 512MB。读了下 SDMC 的配置寄存器 MCR04=0x11200fd6,解出来 CAP 就是 512MB,ECC 开着、SCRAMBLE 加扰也开着,只报 210MB 其实是 AMI 固件按 ECC 容量核算出来的,不是物理限制。
往 210MB 以上写标记做混叠探测,发现那些地址是真内存,能独立读写,写进去也不触发 ECC 错误。再把全盘扫一遍画出完整地图:
0x80000000 - 0x87100000 113MB 好(ECC 保护)
0x87100000 - 0x88f00000 30MB 真坏硅
0x88f00000 - 0x8d200000 67MB 好(ECC 保护)
0x8d200000 - ~0x8ec40000 ~26MB ECC 校验字节存储
0x8f000000 - 0x9f000000 256MB 好(无 ECC 自由区)
0x9f000000 - 0xa0000000 16MB VGA 显存
512MB 里其实只有那 30MB 是真坏的。扫的时候有个坑一定要避开,别去动 ECC 校验区 0x8d200000 到 0x8ec40000 这段,写它会把 U-Boot 自己的校验字节冲掉,U-Boot 当场就崩了只能断电,安全自由区从 0x8f000000 起扫。三段 mem= 把坏块和校验区都跳过去,实测能拿回 436MB:
Memory: 348100K/446464K available
榨到极致:patch 固件做到全 ECC
每次手敲 bootm 太麻烦,我想要的是用到的内存全部 ECC 保护、开机自动起、恢复出厂和网页更新都不丢,所以干脆去反汇编 EPYCD8_P2.20.00.ima 里的 u-boot 段。发现 ECC 基址是硬编码在 0x80000000 的,只能设结束地址,所以 ECC 区只能从头往上罩,罩不过中间那块坏硅。而 ECC 区大小是个硬编码立即数,在 file offset 0x1f80:mov r1,#0x0D200000(210MB),我把它改成 #0x1b000000,也就是 432MB。立即数只能取 16MB 的整数倍,再大就压到 VGA 上了,所以 432 是能一步编码又装得下的上限。
改完配上命名清楚的 env、三段 mem= 跳坏块、saveenv 写进 flash,reset 全自动起:
DRAM: 432 MiB
Kernel command line: ... mem=113M@0x80000000 mem=67M@0x88f00000 mem=192M@0x8f000000 ...
Memory: 287180K/380928K available
380928K 就是 372MB,全部落在 432MB ECC 区里,也就是 372MB 全程 ECC 保护,BMC 服务全起,彻底告别 panic 死循环。
最后一道坎:网页更新的校验
改过的镜像想走 BMC 网页更新刷回去,直接被拒:
Image checksum does not match! Img chksum = 0x55E1C308, calc chksum = 0xBE8D2811
这个校验是两层的,而且整镜像的 CRC 把 FMH 头也算进去了,你改一层就破另一层,绕来绕去。我把 rootfs 里的 flasher 和 libchecksum.so.2 反汇编出来才摸清:第一层是标准 zlib CRC-32 覆盖整个 32MB,但会从流里删掉 5 个字节(是删掉不是清零),就是 0x1ff0017 和 0x1ff0032..35;第二层是 FMH 头那 64 字节相加要 ≡ 0 (mod 256)。
解环的关键就在于这 5 个字节都不进 CRC,所以不自指,改完功能字节之后直接算了填回去就行:
d[0x1ff0032:0x1ff0036] = struct.pack("<I", img_crc(d)) # 层1:写 CRC,这个字段本身被跳过
d[0x1ff0017] = (d[0x1ff0017] - hsum(d)) & 0xff # 层2:调平衡字节让头和归零
顺便说一下,因为校验字段不进 CRC,所以第一次失败日志里 BMC 报的那个 calc chksum 其实就是正确值,你嫌麻烦直接回填也行。搞定这个之后,改过的固件就能从网页正常刷了,恢复出厂和网页更新都不会动 env,等于有了一条不用拆机接 ttl 的后路。
收尾
顺带一提,这块 flash 铺满了 32MB,根本没有第二份完整镜像的空间,所以所谓的 A/B 双固件其实是假的,别手贱去切 B 侧,切过去一样会撞坏块 panic。真正的恢复路径就是网页可刷加上 env 永不丢,比 A/B 实在多了。
总结一下,一块开机 panic 死循环、换新 flash 也救不活的二手 AST2500,最后变成了 372MB 全 ECC、上电自动起、网页可刷、断电和恢复出厂都不丢,512MB 物理里只有那 30MB 是真坏的。固件补丁是 EPYCD8_P2.20.00_eccmax_WEB.ima,一共就改了 9 个字节。1300 的 7532 板 u,这波不亏。