服务:电源管理合盖出现没有睡眠的情况

目标与现状

目标:查清「合盖后没有睡眠 / 整晚发热掉电约 30%」的真因,并把「睡眠触发源」与「唤醒源」固化成声明式配置(主配置 /etc/nixos/modules/system/service/logind.nix,事项清单 logind-data/awake.conf),使开机与重建后策略自动生效。

现状(2026-10-16):配置已落地并生效(世代 …sleep-wake 起,后续 logind-* 世代);唤醒位收敛为 /proc/acpi/wakeup 只剩 XHC0 / XHC3 / XHC4;三个底层缺陷未修(见「边界」)。awake 命令面仍由用户人工梳理,本笔记只记录分析结论与既定配置。

用户给定的标准(原话口径):

类别允许明确排除
睡眠触发源(仅这三种)① 合盖 ② Ctrl+Shift+Win+S 快捷键 ③ 开始菜单「睡眠」电源键触发睡眠、闲置倒计时睡眠
唤醒源(仅这四种)① 开盖 ② 点击笔记本键盘按键 ③ USB 键盘 ④ USB 鼠标电源键唤醒、定时任务或服务自动唤醒、蓝牙键鼠唤醒、指纹唤醒

附加约束:期间不依赖任何需要不断轮询保活的服务。

前置事实(实测)

项实测值取证位置
合盖 → 睡眠2026-10-15T23:40:43 Lid closed. → 23:40:46 PM: suspend entry (s2idle)journalctl(systemd-logind / kernel)
长睡段23:40:46 → 2026-10-16T05:27:40 PM: suspend exit,连续 5h47m(这一段正常)同上
之后的震荡05:27:40 → 08:00 进入约 61 秒一循环(睡 21–22s / 醒约 39s);该夜窗口 PM: suspend entry 共 304 行同上
开盖 / 合盖事件08:00:52 Lid opened. → 08:01:23 Lid closed. → 08:01:36 Lid opened.同上
累计 suspend 统计(现网)success 156、fail 153、failed_freeze 153、failed_resume 156、last_failed_dev 3-1.3、last_failed_errno -16、last_failed_step resume、total_hw_sleep 31101799146(µs,约 8.6 小时)/sys/power/suspend_stats/ 逐字段文件
窗口内日志行数(05:20–08:05)Freezing user space processes aborted 151 行;NBD_DISCONNECT 102 行;usb_dev_resume 153 行;PM: suspend entry 303 行journalctl --since … -g …
唤醒 IRQ / GPEpm_wakeup_irq = 7(pinctrl_amd);GPE 0x0B 在 2.5 小时内触发 660 次、占 SCI 95%/sys/power/pm_wakeup_irq、/sys/firmware/acpi/interrupts/gpe0B
唯一有真实唤醒计数的可唤醒源ADP1(交流适配器)152 次/sys/kernel/debug/wakeup_sources
指纹设备usb 3-1.3(2541:9711,挂 hub 3-1 / XHC1):断开 158 次、resume 失败 154、重枚举 159journal
照片镜像 nbdNBD_DISCONNECT 52 次(两个镜像反复断连)journal
WiFi(耗电主因)每轮醒来做一次完整拆装:152 次重新关联 AP、303 次重启 wpa_supplicantjournal(NetworkManager / wpa_supplicant)

时间线与现象判定

真实症状不是「没有睡眠」,而是睡不住:05:27:40 之后每约 61 秒被踢醒一次(单次成功睡眠只有 21–22 秒),该夜共 304 次 suspend 循环;醒着的那约 39 秒里 WiFi 被反复拉起,这才是整晚发热掉电 30% 的来源,不是应用后台在算。

「合盖后看起来没睡」的观感来自这个循环本身:机器不断在「睡—醒—睡」之间切换,指示灯、风扇、键鼠表现都像一直醒着。

三个底层缺陷(均已取证,未修复)

缺陷现象(日志原文)成因
① 照片镜像 nbd 让冻结阶段作废Freezing user space processes aborted after 0.002 seconds (63/67 tasks refusing to freeze, wq_busy=0);NBD_DISCONNECT 反复出现挂载中的 history-img / history-vhdx 的 nbd socket 在冻结阶段被拆 ⇒ 冻结中止 ⇒ 当次睡眠失败
② CS9711 指纹每次 resume 掉线usb 3-1.3: PM: dpm_run_callback(): usb_dev_resume returns -5;config index 0 descriptor too short (expected 32, got 9);error -71该 USB 设备不声明 remote wakeup,恢复时描述符读取失败 ⇒ 每次恢复都要重新枚举(158 次断开 / 159 次重枚举)
③ GPE 风暴 + ADP1 唤醒GPE 0x0B 2.5 小时内 660 次、占 SCI 95%;pm_wakeup_irq = 7;ADP1 唤醒计数 152ACPI 层反复产生 SCI;唯一有真实计数的可唤醒源是交流适配器(拔插电事件)

已落地的处置(声明式配置)

模块构成:logind.nix(主配置)+ logind.sh(注册系统命令 awake)+ logind-data/awake.conf(事项清单,唯一数据源),由 configuration.nix 导入。

① 睡眠触发源(systemd-logind 策略)

设置值含义
HandleLidSwitch / …ExternalPower / …Dockedsuspend(三项一致)合盖即睡眠,不因插电 / 外接屏改变行为
IdleAction / IdleActionSecignore / 0不使用闲置倒计时睡眠
HandleSuspendKey / …LongPress / HandleHibernateKey / …LongPressignoreACPI 睡眠键 / 休眠键不触发动作(Ctrl+Shift+Win+S 走 KDE 全局快捷键,不受影响)
HandlePowerKey刻意不设置电源键既不是睡眠源,也不属本文件职责

② 唤醒位默认策略(udev 规则)

思路是黑名单式:先按总线整体禁止,再按类别放行 USB HID(ID_USB_INTERFACES 含 :03),这样键盘鼠标插在任何口都能唤醒;开机冷插拔与热插拔都套用。

覆盖范围:USB(整体禁 + HID 放行)、ACPI(电池 PNP0C0A:00、电源键 PNP0C0C:00、睡眠键 PNP0C0E:00)、power_supply(ADP1、hid-* 电池)、platform(RTC PNP0B00:00、alarmtimer.0.auto)、i2c(触摸板 i2c-GXTP7385:00)、thunderbolt(域与根设备)、PCI(无线网卡 / 读卡器 / 空根端口、xHCI1、雷雳 NHI)。

刻意不写规则(保持可唤醒 = 标准里的唤醒源):serio0(内置键盘)、PNP0C0D:00(合盖开关)、xHCI0 / xHCI3 / xHCI4(USB 键鼠可能插在任意一口)。

③ 开机 / 重建即生效

logind-awake-apply.service(oneshot)在开机与重建时按本世代清单快照把运行时重放一遍;restartTriggers 挂在清单文件上,事项一变就重启该服务 ⇒ rebuild 即生效,不必重启机器。它是 oneshot + RemainAfterExit,不是轮询服务。

④ 睡眠钩子(睡前快照 / 醒后记账)

挂在 sleep.target 的既有 sleep-actions.service 里:睡前 awake --snapshot 记录唤醒源计数,醒来 awake --record-wake 把「睡多久 + 唤醒原因」追加到 /var/log/logind-awake.log(与既有 wifi / display / amdgpu 钩子并存)。

实测证据

事项清单共 26 项(允许唤醒 6 / 禁止唤醒 20),逐项运行时读回如下(awake list,2026-10-16 实测):

序号类别标题键文件状态运行时状态
1硬件内置键盘(PS/2)serio:serio0allow允许(enabled)
2硬件合盖开关acpi:PNP0C0D:00allow允许(enabled)
3硬件USB 出口 xHCI0(SoC)pci:0000:65:00.3allow允许(enabled)
4硬件USB 出口 xHCI3(雷雳坞)pci:0000:67:00.3allow允许(enabled)
5硬件USB 出口 xHCI4(雷雳坞)pci:0000:67:00.4allow允许(enabled)
6规则USB 键鼠(按 HID 类别放行,任意口)usb:hid-classallow允许(enabled,当前 1 个对象 7-1.4.2)
7硬件USB 集线器链路(根 hub + 各 hub)usb:hub-chaindeny禁止(disabled,12 个对象)
8硬件蓝牙适配器(Intel AX210)usb:1-5deny禁止(disabled)
9硬件内置摄像头(不声明 remote wakeup)usb:3-1.1deny—(设备不声明 remote wakeup)
10硬件CS9711 指纹(不声明 remote wakeup)usb:3-1.3deny—(设备不声明 remote wakeup)
11硬件触摸板 / 触摸屏i2c:i2c-GXTP7385:00deny禁止(disabled)
12硬件电池事件acpi:PNP0C0A:00deny禁止(disabled)
13硬件电源键(ACPI 固定事件,未必真能屏蔽)acpi:PNP0C0C:00deny禁止(disabled)
14硬件ACPI 睡眠键acpi:PNP0C0E:00deny禁止(disabled)
15硬件交流适配器事件(拔插电)ps:ADP1deny禁止(disabled)
16硬件HID 设备电池事件(键鼠电量)hidbatt:deny禁止(disabled)
17硬件无线网卡根端口pci:0000:00:02.2deny禁止(disabled)
18硬件读卡器根端口pci:0000:00:02.3deny禁止(disabled)
19硬件空根端口pci:0000:00:04.1deny禁止(disabled)
20硬件xHCI1(摄像头 + 指纹所在控制器)pci:0000:65:00.4deny禁止(disabled)
21硬件雷雳 NHIpci:0000:67:00.6deny禁止(disabled)
22硬件雷雳域(热插拔唤醒)tb:domain0deny禁止(disabled)
23硬件雷雳根设备(据点 0-0)tb:0-0deny禁止(disabled)
24计时RTC 闹钟(定时唤醒)platform:PNP0B00:00deny禁止(disabled)
25计时alarmtimer(事件计时)platform:alarmtimer.0.autodeny禁止(disabled)
26定时systemd 定时器 WakeSystem(总闸,核验项)check:timers-wakesystemdeny合规(0 个定时器能唤醒)

另有 16 个运行时唤醒位处于 disabled(没有唤醒能力,未逐个列出)。/proc/acpi/wakeup 收敛结果(只剩承载键鼠的 xHCI 为 enabled):

Device  S-state  Status     Sysfs node
GPP0    S4       *disabled
GPP6    S4       *disabled  pci:0000:00:02.2
GPP7    S4       *disabled  pci:0000:00:02.3
GP11    S4       *disabled
SWUS    S4       *disabled
GP12    S4       *disabled  pci:0000:00:04.1
SWUS    S4       *disabled
XHC0    S4       *enabled   pci:0000:65:00.3
XHC1    S4       *disabled  pci:0000:65:00.4
XHC2    S3       *disabled  pci:0000:67:00.0
XHC3    S3       *enabled   pci:0000:67:00.3
XHC4    S3       *enabled   pci:0000:67:00.4
NHI0    S4       *disabled
NHI1    S3       *disabled  pci:0000:67:00.6

logind-awake-apply 重放结果:生效 24 项,跳过 6 项(运行时无对象 / 器件不声明 remote wakeup / nix 声明项),失败 0 项;systemctl --failed = 0。

管理命令

用途命令
看运行时实况(含「文件状态 vs 运行时状态」两列)awake list
看清单的两种状态awake setting
改运行时(立即生效,不碰清单)sudo awake on <序号…> / sudo awake off <序号…>
把运行时实况固化为清单的状态列(下次 rebuild 生效)sudo awake sync
看唤醒日志(睡多久 + 唤醒原因)awake log [N] [-f]
手工把清单重放到运行时sudo systemctl restart logind-awake-apply
改事项名单 / 序号 / 标题编辑 /etc/nixos/modules/system/service/logind-data/awake.conf 后 nixos-rebuild switch <描述>

机制说明:清单只有一个文件(logind-data/awake.conf)—— nix 读它(rebuild 时进本世代快照)、awake list/setting 读它、awake sync 回写它;on/off 只改运行时。awake 命令面仍在人工梳理中。

踩坑记录

  • udev 规则不允许行尾注释:报 a comma between tokens is expected. 并整条忽略 ⇒ 注释必须单独占行。
  • 唤醒位要按实际宿主设备查:交流适配器的属性在 /sys/class/power_supply/ADP1(power_supply 类),不在 ACPI 的 ACPI0003:00 上。
  • 有 wakeup 属性 ≠ 能唤醒:USB 设备必须先声明 remote wakeup;不声明的连属性都没有(本机内置摄像头、CS9711 指纹就是这种)。
  • 电源键在 ACPI 里是固定事件(/sys/firmware/acpi/interrupts/ff_pwr_btn 显示 invalid),写 PNP0C0C 的 power/wakeup 只是声明意图;真要屏蔽得 mask 该固定事件,而那会连「按电源键」一起废掉 ⇒ 不做,如实标注。
  • nixos-rebuild 在有守卫的机器上必须带世代描述(switch <描述>);构建被 cache.nixos.org 的 SSL connect error (35) 打断时,加 --option substituters "" 走纯本地构建。
  • 两个 nixos-rebuild 会并发抢锁(被中断留下的孤儿 nix-build)⇒ 显式 kill 多余那个的 PID,保留已构建产物复用。
  • nix 声明的单元在 /etc/systemd/system 下是只读符号链接:disable 报 Read-only file system、mask 报 already exists and is a symlink ⇒ 用 stop + mask --runtime(写 /run)才真能停住;错误必须如实上报(第一版把 stderr 丢 /dev/null,导致假报成功)。
  • --apply 的退出码语义:只有「真写失败」才算失败;「运行时未在位 / 键无效 / 器件不声明 remote wakeup / nix 声明项」应跳过并留一行原因 —— 第一版把后者记成失败 ⇒ oneshot 在激活阶段 FAILURE、rebuild 退出码 4。
  • 新内核的 /sys/power/suspend_stats 是目录(逐字段文件,时间字段单位 µs),旧接口是单文件、以秒计 ⇒ 读之前先 ls。
  • 不要给「nix 读的数据」再往 /etc 播种一份运行时副本:曾出现两份清单(源文件 + /etc 副本),用户改源文件后命令面看不到变化,还得靠「双向播种」硬凑 ⇒ 改为单一数据源,那层副本与 --seed 机制已删除。
  • 不要自加「看似智能的轮询自愈」:照片镜像原有的 2 分钟看门狗(nbd 服务没了就重连)会被用户视为「强迫侵入用户管理」⇒ 已按用户要求删除,服务半死改为手动 systemctl restart history-{img,vhdx}-attach。

边界

  • 未修复:① nbd 冻结失败(照片镜像挂载期间的睡眠必然失败)② CS9711 指纹 resume 掉线 ③ GPE 0x0B 风暴与 ADP1 唤醒 —— 本次只收敛了唤醒源,这三项留给后续处理。
  • 需物理动作的验证未做:合盖后点 USB 键鼠 / 按电源键是否唤醒(须人在机器旁)。
  • 照片镜像:2 分钟轮询看门狗已删除 ⇒ nbd 服务半死时需手动 systemctl restart history-{img,vhdx}-attach;是否自动挂载由 nix 开关 local.photoArchive.{img,vhdx}.automount 决定(KDE 的「设备自动挂载器」会在设备出现时自动挂载,且无法按单设备抑制,故改为「是否暴露设备」由该开关控制)。
  • 版本记录:/etc/nixos 不是 git 仓库(工作区才是)⇒ 本模块改动没有 git 历史;同组证据另存于项目文档 docs/power-suspend-loop.md。
  • awake 命令面(列口径、on/off 与 sync 语义、清单文件位置)仍在用户人工梳理中,本笔记只记录当前实现与分析结论。