一台 HP ProDesk 400 G5 Desktop Mini 跑 Debian,会随机硬重启,任何日志里都找不到原因。最后定位到 Intel i915 显示驱动的 DC5/DC6 显示省电状态缺陷(Debian Bug #1050852,该 bug 报告点名了这款机型)。修复方式是内核参数
i915.enable_dc=0。但这个故事真正值得记下来的不是结论,而是一个反复出现的模式:这台机器此前已经打过三轮补丁,分别封掉了 CPU、PCIe、NVMe 三条低功耗通路——唯独漏了显示电源那一条。
一、症状:会自己重启的小主机
机器配置:
| 项目 | 型号 |
|---|---|
| 主机 | HP ProDesk 400 G5 Desktop Mini |
| CPU | Intel Core i3-9100T(Coffee Lake,35W) |
| 内存 | 8 GB |
| 存储 | SK hynix BC711 256 GB NVMe |
| 系统 | Debian 13 (trixie),kernel 6.12.94 |
| 负载 | Docker 跑 open-webui / mihomo,Tailscale 组网 |
症状描述起来很朴素:它会自己重启。
不是服务崩了重启,是整机重启。ICMP 断、SSH 断、容器全部重来,然后自己爬起来。查 last -x,最近十几次启动全部标着 crash:
reboot system boot 6.12.94+deb13-amd64 Tue Sep 22 21:07 - still running
reboot system boot 6.12.94+deb13-amd64 Tue Sep 22 19:23 - crash
reboot system boot 6.12.94+deb13-amd64 Sat Sep 12 20:12 - crash
reboot system boot 6.12.94+deb13-amd64 Sat Sep 12 18:18 - crash
reboot system boot 6.12.94+deb13-amd64 Sat Sep 12 12:56 - crash
...crash 意思是没有关机记录——不是 shutdown,是断电或硬复位。
最惨的一段是 9 月 3 日下午:14:22 到 18:37 三个多小时里,机器重启了约 40 次,平均每两分钟一轮。
二、第一步不是查故障,是先给它分类
面对"日志全空的死机",最忌讳的就是一上来猜电源猜内存。先做一件事:判断故障发生在哪一层。
第一条判据是全量日志检索:
journalctl -k --no-pager | grep -iE "panic|Oops|BUG:|machine check|mce|hung task|soft lockup|rcu.*stall"命中数:0。
这个"0"信息量极大。它意味着:不是内核 panic,不是空指针,不是内存位翻转被 CPU 抓到,不是进程卡死。故障点在内核能记录日志的层级之下——CPU 是瞬间停止取指的。
第二条判据是看它为什么能"自己爬起来":
systemd[1]: Using hardware watchdog 'iTCO_wdt', version 6, device /dev/watchdog0
systemd[1]: Watchdog running with a hardware timeout of 1min.主板上有硬件看门狗(iTCO_wdt),systemd 把它设成了 60 秒(/etc/systemd/system.conf 里 RuntimeWatchdogSec=60)。机器一旦卡死,60 秒后看门狗直接拉复位。
所以"自动重启"根本不是系统在自愈,是看门狗在给一具尸体做心肺复苏。 这也解释了为什么日志永远是空的——复位发生在内核有机会写下任何东西之前。
顺带一提:RuntimeWatchdogSec=60 这个配置本身很好,对一台无人值守的机器来说,"卡死→60 秒后自动恢复"远好于"卡死→永远躺着"。不要因为排查方便就去关它。两条判据合起来,结论明确:这是硬复位,不是软件崩溃。 接下来该往硬件和固件方向看,而不是翻应用日志。
三、最反常的一条证据:满载稳如老狗,闲下来就死
排查的前一任(也是我)在这台机器上留下了一堆压测脚本和报告。最有价值的一份是一次连续 4 小时的 stress-ng 压测记录:
--- Round 8/8 ---
load=14.76 14.61 14.60 freq=3399998kHz temp1_input=77 temp2_input=78 ...
Throttle: 12263 / 5199
Round 8 完成 (exit=0)4 核全满载、负载 14–15、CPU 恒定 3.4 GHz、封装温度推到 79–80°C,八轮全部正常结束,一次没崩。
而崩溃时的状态长这样(来自机器上自建的功耗遥测 power-telemetry.log):
2026-09-22 21:49:18 up=2530s load=0.10 0.10 0.09 freq=1100002KHz pkg=3.0W temp=43C负载 0.1、频率 1.1 GHz、封装功耗 3 W、温度 43°C。
满载不崩,空闲才崩。 这个反差排除了几乎所有"传统"嫌疑:不是散热、不是供电功率不足、不是 CPU 体质问题、也不是软件在高负载下踩了坑。
它把矛头精确地指向了一类东西:低功耗状态的进入/退出。
四、埋在重启史里的那个数字:120 秒
把 9 月 3 日死循环期间每一次启动的存活时长算出来:
86s 119s 120s 121s 122s 131s 133s 152s 154s 120s 122s ...密集聚集在 120 秒附近,散布到 155 秒。
这个分布不是随机的。如果是电源纹波或内存偶发错误,时长应该散得很开;如果是负载触发,应该和负载高峰相关。而"稳定地在某一个秒数附近死"意味着:有个周期性状态机走到了某一步就回不来了。
这是一条非常有价值的指纹。后来它成了确认根因的关键一环。
五、这台机器其实已经打过三轮补丁,而且方向是对的
翻 /etc/default/grub 的修改时间,是 2026-09-03 18:36:20——正好卡在死循环的最后一轮。
那个时间点前后,机器被人加上了这四个内核参数:
nvme_core.default_ps_max_latency_us=0 ← 关掉 NVMe 自主电源状态切换(APST)
pcie_aspm=off ← 关掉 PCIe 链路省电(ASPM)
intel_idle.max_cstate=1 ← 限制 CPU 只进 C1,不进深 C-state
processor.max_cstate=1 ← 同上,ACPI 侧再兜一层补齐完之后重启,崩溃频率的变化是戏剧性的:从"每 2 分钟一次"降到"9 天里 8 次"。
处理量下降了两个数量级——但没归零。
"部分有效"是这里信息量最大的一句话。 它同时说明了两件事:
- 方向完全正确:故障确实和低功耗状态有关。
- 没打全:还有一条通路漏在外面。
这个时候答案其实已经在纸面上了。把机器里所有会"进入省电状态"的子系统铺开列一遍:
| 子系统 | 省电机制 | 是否已封 |
|---|---|---|
| CPU | 包级 C-state(C1 以上) | ✅ intel_idle.max_cstate=1 |
| PCIe 链路 | ASPM (L0s/L1) | ✅ pcie_aspm=off |
| NVMe SSD | 自主电源状态 APST | ✅ nvme_core.default_ps_max_latency_us=0 |
| SATA | ALPM | ➖ 机器没接 SATA 盘 |
| 显示引擎 | DC5 / DC6 | ❌ 没封 |
最后那一行,就是漏掉的一条。
六、dmesg 里的那行字
去内核日志里找显示相关的信息:
dmesg | grep -iE "dmc|i915"[ 1.896726] i915 0000:00:02.0: [drm] Found COFFEELAKE (device ID 3e91) display version 9.00
[ 1.947945] i915 0000:00:02.0: [drm] Finished loading DMC firmware i915/kbl_dmc_ver1_04.bin (v1.4)
[ 2.749653] i915 0000:00:02.0: [drm] [ENCODER:98:DDI A/PHY A] failed to retrieve link info, disabling eDP第二行那个文件名——i915/kbl_dmc_ver1_04.bin——就是 Debian Bug #1050852 标题里点名的那一个:
Bug#1050852 — firmware-misc-nonfree: System crashes and reboots when i915/kbl_dmc_ver1_04.bin is present再确认一下这个省电状态是不是被禁用了:
$ cat /sys/module/i915/parameters/enable_dc
-1 # -1 = auto,即"允许进入 DC5/DC6"
$ cat /proc/cmdline | grep -o 'i915.enable_dc=[0-9]*'
(无输出) # 确认从未打过这个补丁CPU、PCIe、存储三条通路的省电状态都封上了,唯独显示电源那条一直开着——相当于把家里所有窗户都装了锁,阳台门却敞着。
根因完整链条
显示输出未真正点亮(DP 口有接线,但显示器未上电/待机)
↓
i915 载入 DMC 固件 kbl_dmc_ver1_04.bin
↓
显示通路空闲时,驱动照常进入 DC5 / DC6 显示省电状态
↓
显示电源井(power well)切换异常
↓
SoC 瞬间停摆,CPU 停止取指,内核来不及写任何日志
↓
60 秒后 iTCO 硬件看门狗拉复位
↓
机器重启,journal 断在半行,last 记为 crash七、这个 bug 为什么特别难查
值得单独说一段,因为它解释了为什么前面三轮排查会走那么多弯路。
第一,它会自我隐藏。 这个缺陷的前提是"显示输出已连接但没有真正工作"。只要你接上显示器并把它点亮,机器就稳如磐石——反复验证都是这个结果。所以任何一个负责排查的人,把机器从机柜里搬出来、接上显示器准备"好好测一测"的时候,故障就已经消失了。你测的是一个已经不复现的系统。
第二,它不留任何痕迹。 故障发生在电源落差切换的那一瞬间,CPU 直接停摆。不是"先记录再死",是"根本没有记录的机会"。dmesg 干净、journal 干净、连 HP 的 BIOS 事件日志里都不会留下"异常关机"的记录。
第三,它长得太像典型的电源故障。 无日志 + 随机 + 自动重启,这三个特征凑在一起,任何人第一反应都是电源适配器老化、内存坏了、主板供电问题。这三个方向都可以查到天荒地老。
自查方法(如果你也有 HP 的小主机跑 Linux):
# 1. 有没有加载这个 DMC 固件
dmesg | grep -i dmc
# 2. 显示省电状态是不是开着的
cat /sys/module/i915/parameters/enable_dc # -1 = auto(有风险)
# 3. 显示口状态:connected 但显示器没亮 = 典型触发条件
for s in /sys/class/drm/card*/status; do echo -n "$s = "; cat $s; done八、修复:一个参数
# 1. 备份(这一步不能省)
cp /etc/default/grub /etc/default/grub.bak.$(date +%Y%m%d-%H%M%S)
# 2. 追加参数,保留原有全部参数
sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"/GRUB_CMDLINE_LINUX_DEFAULT="\1 i915.enable_dc=0"/' \
/etc/default/grub
# 3. 重新生成引导配置
update-grub
# 4. 确认参数真的写进去了
grep -c 'i915\.enable_dc=0' /boot/grub/grub.cfg # 必须 > 0
# 5. 重启
reboot重启后的复测,三项判据缺一不可:
$ cat /proc/cmdline | grep -o 'i915.enable_dc=0'
i915.enable_dc=0 # ① 参数已生效
$ cat /sys/module/i915/parameters/enable_dc
0 # ② -1 → 0,省电状态已禁用
$ last -x reboot shutdown | head -3
reboot system boot 6.12.94+deb13-amd64 Tue Sep 22 21:57:20 2026 - still running
reboot system boot 6.12.94+deb13-amd64 Tue Sep 22 21:07:10 2026 - 21:57:04 (00:49)
shutdown system down 6.12.94+deb13-amd64 Tue Sep 22 21:57:04 2026 - 21:57:20 (00:00)
# ③ 本次是干净关机,不是 crash重启前请务必做一道预检
改完 update-grub 之后别急着敲 reboot。先确认 GRUB_DEFAULT 指向的启动项仍然存在——如果重新生成的 ID 对不上,grub 会静默回退到 0 号项,你可能就在不知不觉中启动到了别的内核:
grep '^GRUB_DEFAULT' /etc/default/grub
grep -F "gnulinux-6.12.94+deb13-amd64-advanced-<你的UUID>" /boot/grub/grub.cfg这里有个小坑:Debian 生成的grub.cfg用的是$menuentry_id_option 'id'语法,不是--id 'id'。如果你用grep -- "--id 'xxx'"去检查,会得到"不存在"的假警报,白白吓自己一跳。
代价与回滚
代价是空闲功耗会涨 1–2 W(不再进入 DC5/DC6)。对一台 7×24 的服务器,用这点电换稳定性完全值得。DMC 固件本身不需要删除,参数只是禁止驱动进入 DC 状态。
# 回滚
cp /etc/default/grub.bak.<时间戳> /etc/default/grub && update-grub && reboot加固:十块钱买个保险
加一个 DP/HDMI 假负载(EDID 模拟器) 插在视频口上。驱动会始终认为存在一个有效显示输出,从源头绕过这个缺陷。
对放在机柜角落、平时根本不接显示器的机器尤其值得——万一将来某次系统升级把引导参数覆盖了,这一层还能兜住。
九、验证:借 SSD 的计数器当裁判
修完之后最大的问题是:怎么知道真的好了?
机器随机崩溃,你不能靠"等它两天没崩"来下结论。好在有一个天然计数器:
$ smartctl -A /dev/nvme0 | grep -i "Unsafe Shutdowns"
Unsafe Shutdowns: 984984 次非正常关机。 这个数字是这块 SSD 从出厂到现在的累计账本——每一次硬复位都会加一。
它的好处是:客观、单调递增、不受重启影响(正常关机不计数)。修复前后的复测里,这个数字停在 984 没动,说明我们的重启是干净的。
于是结论判据变得非常简单:观察 7–14 天,这个数字不增长,就结案。
(顺便,984 这个数字本身也是很直观的现场记录——它告诉你这台机器在它的一生中,以平均每天 4 次以上的频率被硬生生拉断过电。)
十、顺带发现的其它问题
一次彻底排查总会顺手捡出一堆东西,如实记录:
1. 一个空 USB 端口上的"幽灵设备"
机器上除内置蓝牙外没有其它 USB 设备,但每次启动内核都在 1 号总线的第 10 号端口上反复失败:
usb 1-10: new low-speed USB device number 14 using xhci_hcd
usb 1-10: device descriptor read/64, error -71
usb usb1-port10: attempt power cycle
usb 1-10: device not accepting address 16, error -71
usb usb1-port10: unable to enumerate USB deviceerror -71 是 EPROTO。空端口上出现幽灵设备、还触发端口掉电重试,是接口或前面板排线硬件异常的表现。(不是本次崩溃的根因——崩溃在空闲低负载时发生,与 USB 活动没有时间相关性——但该查。)
2. 每天静默失败的维护脚本
机器上有个自建的 mt-daily.service,本意是每天做一次自愈:重启卡住的服务、docker start 挂掉的容器、记录温度。但它以非特权用户运行,里面调的 systemctl restart opencode 每次都被权限挡下:
Failed to restart opencode.service: Interactive authentication required.于是这套"日常自愈机制"其实一直是个空壳,只是没人看日志所以没人发现。
3. 内核被置了 taint 位
修复后 dmesg 里多了一行:
i915 0000:00:02.0: [drm] Setting dangerous option enable_dc - tainting kerneli915 把这个调试参数标记为 "dangerous",加载它会置一个 kernel taint 标志位。只影响标识,没有功能影响——但如果你将来要给内核提 bug report,需要说明这个 taint 的来源。
十一、几条可以迁移的经验
这次排查里真正可复用的,不是"i915 有 bug"这个知识,而是下面这些方法:
1. 先分类,再排查。
"日志全空"本身就是一条结论,它把范围从"所有可能"缩到"内核之下的硬复位"。花五分钟搞清楚故障属于哪一层,比盲目试三天有效。
2. "部分有效"比"完全无效"信息量大得多。
前一轮补丁把崩溃频率压了两百倍但没归零——这不是"方向错了",这是"方向对了但没打全"。这种情况下最该做的是把同类的所有子系统列成一张表,逐条对照哪里还是空的。
3. 时长分布是指纹。
"稳定地在某个固定秒数附近死"和"随机时间死"指向完全不同的原因。前者是状态机/超时,后者才可能是电源纹波或内存。
4. 别浪费机器上的累计计数器。
SSD 的 Unsafe Shutdowns、网卡的错误计数、dmesg 的 throttling 计数……这些单调递增的数字是天然的"会不会修好"判据,比"观察两天看看"可靠得多。
5. 先读机器上已有的脚本。
这台机器上已经有前一任排查者留下的功耗遥测和启动监控脚本。它们不只是数据来源——脚本本身就在说"上一任怀疑什么"。看到有人在 2 秒一次地采样封装功耗,你就知道他已经怀疑是电源状态问题了。
6. 硬死机的第一件事是建立取证能力,而不是继续猜。
这类故障默认不留现场。真要继续深挖,正确顺序是:
- netconsole:把内核日志实时经 UDP 推到另一台常开机器。硬死机时已发出的报文不会丢——这是唯一能抓住"卡死前最后一帧"的手段。
- pstore / ACPI ERST:让 panic 现场跨重启保留(先确认
/sys/fs/pstore/是否真的空着)。 - 把
kernel.hung_task_timeout_secs调到早于看门狗触发,好在复位前打印一次阻塞栈。
7. 别在结案前清理日志。
这台机器的 journal 有 771 MB,看着很占地方。但那里面存着约 25 次启动的完整历史——那是判断修复是否彻底的核心证据。等 7–14 天观察期过了再 vacuum,不迟。
附:一把梭取证脚本
如果你手上也有台"会自己重启但什么都不说"的 Linux 机器,这套命令可以直接抄。
export LC_ALL=C
# === 1. 重启史:注意 crash 标记与启动间隔 ===
last -x -F | head -80
journalctl --list-boots | tail -30
# === 2. 判定"软崩"还是"硬复位"(全空 = 硬复位)===
journalctl -k --no-pager | grep -iE \
"panic|Oops|BUG:|machine check|mce|hung task|soft lockup|rcu.*stall|taint"
# === 3. 硬件与固件 ===
dmidecode -s system-product-name
dmidecode -s bios-version; dmidecode -s bios-release-date
lspci -nn | grep -iE "vga|display"
dmesg | grep -iE "dmc|i915|firmware"
# === 4. 崩溃计数器与健康度 ===
smartctl -H -A /dev/nvme0 | grep -iE \
"Unsafe Shutdowns|Power Cycles|Power On Hours|Media and Data Integrity|Percentage Used"
sensors
# === 5. 看门狗 ===
grep -iE "watchdog" /etc/systemd/system.conf /etc/systemd/system.d/* 2>/dev/null
# === 6. 低功耗通路全景(最关键)===
cat /proc/cmdline
cat /sys/module/i915/parameters/enable_dc # 显示 DC5/DC6
for s in /sys/class/drm/card*/status; do echo -n "$s = "; cat $s; done
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/name # CPU 可用 C-state
# === 7. USB 幽灵设备 ===
journalctl -k --no-pager | grep -iE "usb .*error -71|attempt power cycle|unable to enumerate"
# === 8. 上次启动的最后几行(断点在哪)===
journalctl -b -1 --no-pager | tail -50
# === 9. 有没有跨重启留下的现场 ===
ls -la /sys/fs/pstore/ /var/crash/配置速查(本次修复后的最终状态)
内核参数:
quiet nvme_core.default_ps_max_latency_us=0 pcie_aspm=off
intel_idle.max_cstate=1 processor.max_cstate=1 i915.enable_dc=0
判据:/sys/module/i915/parameters/enable_dc == 0
验收:smartctl -A /dev/nvme0 | grep "Unsafe Shutdowns" 7–14 天不增长(本次排查仅做了只读采集,最终改动只有内核参数一行。原配置已备份,回滚命令已记录。)