Debian 13 工控机 ACPI BIOS Error:从定位到永久修复
背景
一台采用 Intel J1900 平台的工控机主板启动 Debian 时,内核持续报告 ACPI 错误:
ACPI BIOS Error (bug): Could not resolve symbol [\_SB._OSC.CDW1], AE_NOT_FOUND
ACPI Error: Aborting method \_SB._OSC due to previous error (AE_NOT_FOUND)系统能够继续启动,网络和服务也正常,但这并不代表 BIOS 中的 ACPI 代码没有问题。本文记录如何定位错误、修补 DSDT,并让补丁在系统和内核升级后自动生效。
本文对应的硬件和软件环境:
主板厂商:DS
主板型号:DS FJ04D
处理器平台:Intel J1900
BIOS 厂商:American Megatrends Inc.
BIOS 日期:2020-09-10
BIOS 界面版本:T60K0M11 T2
系统:Debian 13.6
内核:6.12.96+deb13-amd64
启动方式:UEFI重要:本文生成的 DSDT 只适用于提取它的这块主板和当前 BIOS。不要把文中的 DSDT.aml 用到其他主板,即使处理器和接口看起来相同。1. 确认错误来源
查看完整的内核日志:
journalctl -k -b --no-pager |
grep -i -A10 -B5 -E 'ACPI BIOS Error|AE_NOT_FOUND|Could not resolve'关键输出如下:
ACPI BIOS Error (bug): Could not resolve symbol [\_SB._OSC.CDW1], AE_NOT_FOUND
ACPI Error: Aborting method \_SB._OSC due to previous error (AE_NOT_FOUND)\_SB._OSC 是固件与操作系统协商平台能力的方法。AE_NOT_FOUND 表示该方法运行时引用了尚未定义的 CDW1 对象。这是 BIOS 提供的 AML 代码错误,不是 Debian 安装损坏。
查看硬件信息:
dmidecode -s system-manufacturer
dmidecode -s system-product-name
dmidecode -s baseboard-manufacturer
dmidecode -s baseboard-product-name
dmidecode -s bios-vendor
dmidecode -s bios-version
dmidecode -s bios-release-date如果当前用户没有 sudo,可以先执行 su - 切换到 root。本文后续命令均以 root 身份执行。
2. 提取并反编译 ACPI 表
安装 ACPICA 工具:
apt update
apt install acpica-tools提取 ACPI 表:
mkdir -p /root/acpi-fix
cd /root/acpi-fix
acpidump -b同时加载 SSDT,反编译 DSDT:
iasl -e ssdt*.dat -d dsdt.dat
cp dsdt.dsl dsdt.backup.dsl查找 _OSC 和 CDW1:
grep -n -A80 -B10 'Method (_OSC' dsdt.dsl
grep -n -A10 -B10 'CDW1' dsdt.dsl3. ACPI 错误的真正原因
主板 DSDT 中存在两个 _OSC 方法。\_SB.PCI0._OSC 的写法正确,出错的是平台级 \_SB._OSC。
原始代码的结构如下:
Method (_OSC, 4, NotSerialized)
{
If ((Arg0 == ToUUID ("0811b06e-4a27-44f9-8d60-3cbbc22e7b48")))
{
CreateDWordField (Arg3, Zero, CDW1)
CreateDWordField (Arg3, 0x04, CDW2)
// ...
}
Else
{
CDW1 |= 0x04
Return (Arg3)
}
}CDW1 只在 UUID 匹配的 If 分支里创建,但 UUID 不匹配的 Else 分支同样会访问它。Linux 传入另一 UUID 时直接进入 Else,此时 CDW1 不存在,于是出现 AE_NOT_FOUND。
修复方式是把 CDW1 的创建移到条件判断之前:
Method (_OSC, 4, NotSerialized)
{
CreateDWordField (Arg3, Zero, CDW1)
If ((Arg0 == ToUUID ("0811b06e-4a27-44f9-8d60-3cbbc22e7b48")))
{
CreateDWordField (Arg3, 0x04, CDW2)
// ...
}
Else
{
CDW1 |= 0x04
Return (Arg3)
}
}这样无论进入哪个分支,CDW1 都已经存在。
4. 处理新版 iasl 检出的旧代码
重新编译时,新版 iasl 还发现原厂 DSDT 中有一处与 _OSC 无关的旧式写法:MCHK 方法动态创建 DCFE 字段,另一个方法却通过 ^^MCHK.DCFE 跨方法访问它。
错误类似:
Object is created temporarily in another method and cannot be accessed (^^MCHK.DCFE)原始方法:
Method (MCHK, 0, Serialized)
{
If ((MADR != 0xFFFFFFFF))
{
OperationRegion (IGMM, SystemMemory, MADR, 0x3000)
Field (IGMM, AnyAcc, NoLock, Preserve)
{
Offset (0x20C8),
, 4,
DCFE, 4
}
}
}将它改成直接返回字段值:
Method (MCHK, 0, Serialized)
{
If ((MADR != 0xFFFFFFFF))
{
OperationRegion (IGMM, SystemMemory, MADR, 0x3000)
Field (IGMM, AnyAcc, NoLock, Preserve)
{
Offset (0x20C8),
, 4,
DCFE, 4
}
Return (DCFE)
}
Return (Zero)
}然后把:
DerefOf (CDCT [^^MCHK.DCFE])改成:
DerefOf (CDCT [MCHK ()])这一步只是为了让旧 DSDT 能通过新版编译器检查,并不是最初 ACPI 报错的根因。
5. 提升 OEM Revision
Linux 的 ACPI table upgrade 机制不会用相同版本的表覆盖 BIOS 表。原厂 DSDT 的 OEM Revision 是:
0x01072009因此需要把 DefinitionBlock 中的版本提高,例如:
DefinitionBlock ("", "DSDT", 2, "ALASKA", "A M I ", 0x0107200A)可以精确替换第一次出现的版本号:
sed -i '0,/0x01072009/s//0x0107200A/' dsdt.dsl
grep -m1 'DefinitionBlock' dsdt.dsl重新编译:
iasl -tc dsdt.dsl 2>&1 | tee compile.log
grep -E 'Compilation successful|Compilation failed' compile.log预期结果:
Compilation successful. 0 Errors检查编译后 AML 的 OEM Revision:
od -An -tx4 -j24 -N4 dsdt.aml预期输出:
0107200a编译器仍可能报告原厂表中的 Warnings 和 Remarks,只要最终是 0 Errors 并生成 dsdt.aml,就不妨碍继续测试。
6. 制作一次性 initramfs override
内核要求 ACPI override 位于 initramfs 最前面的未压缩 newc CPIO 中,路径必须是:
kernel/firmware/acpi/DSDT.aml制作 CPIO:
cd /root/acpi-fix
mkdir -p initrd-override/kernel/firmware/acpi
cp dsdt.aml initrd-override/kernel/firmware/acpi/DSDT.aml
cd initrd-override
find kernel -print | cpio --quiet -H newc -o > ../acpi_override.cpio
cd ..保留当前内核的原始 initramfs,然后将 CPIO 放到最前面:
KVER="$(uname -r)"
cp -a "/boot/initrd.img-$KVER" "/boot/initrd.img-$KVER.no-acpi"
cat acpi_override.cpio "/boot/initrd.img-$KVER.no-acpi" \
> "/boot/initrd.img-$KVER"确认文件已进入 initramfs:
lsinitramfs "/boot/initrd.img-$KVER" |
grep 'kernel/firmware/acpi/DSDT.aml'7. 重启并验证
重启后检查:
journalctl -k -b --no-pager |
grep -iE 'Table Upgrade|Physical table override|AE_NOT_FOUND|ACPI BIOS Error'成功时应看到:
ACPI: Table Upgrade: override [DSDT-ALASKA- A M I ]
ACPI: DSDT ... Physical table override, new table: ...并且不再出现:
AE_NOT_FOUND
ACPI BIOS Error还应检查服务和关键设备:
systemctl --failed
ip link
journalctl -k -b -p err --no-pager8. 让内核升级后自动应用补丁
普通系统升级不会修改 BIOS,但安装新内核或执行 update-initramfs 会重新生成 initramfs。为了避免补丁丢失,可以安装一个 post-update 钩子。
保存已经验证过的 CPIO:
mkdir -p /etc/initramfs/post-update.d
mkdir -p /usr/local/lib/acpi
cp /root/acpi-fix/acpi_override.cpio \
/usr/local/lib/acpi/acpi_override.cpio创建 /etc/initramfs/post-update.d/acpi-dsdt-override:
#!/bin/sh
set -eu
KVER="${1:-}"
INITRD="${2:-/boot/initrd.img-$KVER}"
ARCHIVE="/usr/local/lib/acpi/acpi_override.cpio"
[ -n "$KVER" ] || exit 0
[ -f "$INITRD" ] || exit 0
[ -f "$ARCHIVE" ] || exit 0
if lsinitramfs "$INITRD" 2>/dev/null |
grep -qx 'kernel/firmware/acpi/DSDT.aml'
then
exit 0
fi
cp -a "$INITRD" "$INITRD.no-acpi"
cat "$ARCHIVE" "$INITRD.no-acpi" > "$INITRD.acpi-new"
chmod --reference="$INITRD.no-acpi" "$INITRD.acpi-new"
chown --reference="$INITRD.no-acpi" "$INITRD.acpi-new"
mv "$INITRD.acpi-new" "$INITRD"授权并检查:
chmod 0755 /etc/initramfs/post-update.d/acpi-dsdt-override
sh -n /etc/initramfs/post-update.d/acpi-dsdt-override测试:
update-initramfs -u -k "$(uname -r)"
lsinitramfs "/boot/initrd.img-$(uname -r)" |
grep 'kernel/firmware/acpi/DSDT.aml'update-initramfs 在 J1900 上可能需要几分钟,不要中途按 Ctrl+C。
9. 本次排障中的两个关键坑
只找到表,不代表已经覆盖
第一次启动时日志只有:
ACPI: DSDT ACPI table found in initrd但错误仍然存在。这表示内核发现了新表,却没有采用。原因是修补表与原厂表的 OEM Revision 相同。提高 OEM Revision 后,日志才出现 Table Upgrade: override。
Secure Boot 并不是本次原因
可以通过以下命令检查相关条件:
grep CONFIG_ACPI_TABLE_UPGRADE "/boot/config-$(uname -r)"
cat /sys/kernel/security/lockdown 2>/dev/null || true
mokutil --sb-state本机结果是:
CONFIG_ACPI_TABLE_UPGRADE=y
[none] integrity confidentiality
SecureBoot disabled因此内核支持 override,且没有被 Secure Boot 或 lockdown 阻止。
10. 回退和维护
每次钩子运行都会保留未注入补丁的文件:
/boot/initrd.img-<内核版本>.no-acpi同时应保留:
/root/acpi-fix/
/usr/local/lib/acpi/acpi_override.cpio
/etc/initramfs/post-update.d/acpi-dsdt-override如果新内核启动异常,可以在 GRUB 的 Advanced options for Debian 中选择旧内核。
如果以后升级主板 BIOS,应先停用钩子,因为新版 BIOS 的 DSDT 可能已经变化或已修复问题:
chmod 0644 /etc/initramfs/post-update.d/acpi-dsdt-override
update-initramfs -u -k all更换主板后也必须移除该补丁。DSDT 与具体主板和 BIOS 强绑定,不能跨硬件复用。
总结
这次故障的根因不是 Linux ACPI 实现,而是 BIOS 的 \_SB._OSC 方法存在变量作用域错误:Else 分支引用了只在 If 分支中创建的 CDW1。
最终方案没有使用 acpi=off、pci=noacpi 等可能破坏电源管理和中断路由的启动参数,也没有隐藏日志,而是:
- 提取原厂 ACPI 表;
- 修复 DSDT 中的 AML 逻辑;
- 提高 OEM Revision;
- 通过 Linux ACPI table upgrade 覆盖原厂 DSDT;
- 使用 initramfs post-update 钩子保证内核升级后自动生效。
最终内核明确确认:
ACPI: Table Upgrade: override
ACPI: Physical table override原来的 \_SB._OSC.CDW1 / AE_NOT_FOUND 错误随之消失。