一台 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
CPUIntel 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 次"。

处理量下降了两个数量级——但没归零。

"部分有效"是这里信息量最大的一句话。 它同时说明了两件事:

  1. 方向完全正确:故障确实和低功耗状态有关。
  2. 没打全:还有一条通路漏在外面。

这个时候答案其实已经在纸面上了。把机器里所有会"进入省电状态"的子系统铺开列一遍:

子系统省电机制是否已封
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
SATAALPM➖ 机器没接 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:                   984

984 次非正常关机。 这个数字是这块 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 device

error -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 kernel

i915 把这个调试参数标记为 "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 天不增长

(本次排查仅做了只读采集,最终改动只有内核参数一行。原配置已备份,回滚命令已记录。)

最后修改:2026 年 09 月 26 日
如果觉得我的文章对你有用,请随意赞赏