目标与现状
目标:查清「合盖后没有睡眠 / 整晚发热掉电约 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 / GPE | pm_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、重枚举 159 | journal |
| 照片镜像 nbd | NBD_DISCONNECT 52 次(两个镜像反复断连) | journal |
| WiFi(耗电主因) | 每轮醒来做一次完整拆装:152 次重新关联 AP、303 次重启 wpa_supplicant | journal(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 唤醒计数 152 | ACPI 层反复产生 SCI;唯一有真实计数的可唤醒源是交流适配器(拔插电事件) |
已落地的处置(声明式配置)
模块构成:logind.nix(主配置)+ logind.sh(注册系统命令 awake)+ logind-data/awake.conf(事项清单,唯一数据源),由 configuration.nix 导入。
① 睡眠触发源(systemd-logind 策略)
| 设置 | 值 | 含义 |
|---|---|---|
HandleLidSwitch / …ExternalPower / …Docked | suspend(三项一致) | 合盖即睡眠,不因插电 / 外接屏改变行为 |
IdleAction / IdleActionSec | ignore / 0 | 不使用闲置倒计时睡眠 |
HandleSuspendKey / …LongPress / HandleHibernateKey / …LongPress | ignore | ACPI 睡眠键 / 休眠键不触发动作(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:serio0 | allow | 允许(enabled) |
| 2 | 硬件 | 合盖开关 | acpi:PNP0C0D:00 | allow | 允许(enabled) |
| 3 | 硬件 | USB 出口 xHCI0(SoC) | pci:0000:65:00.3 | allow | 允许(enabled) |
| 4 | 硬件 | USB 出口 xHCI3(雷雳坞) | pci:0000:67:00.3 | allow | 允许(enabled) |
| 5 | 硬件 | USB 出口 xHCI4(雷雳坞) | pci:0000:67:00.4 | allow | 允许(enabled) |
| 6 | 规则 | USB 键鼠(按 HID 类别放行,任意口) | usb:hid-class | allow | 允许(enabled,当前 1 个对象 7-1.4.2) |
| 7 | 硬件 | USB 集线器链路(根 hub + 各 hub) | usb:hub-chain | deny | 禁止(disabled,12 个对象) |
| 8 | 硬件 | 蓝牙适配器(Intel AX210) | usb:1-5 | deny | 禁止(disabled) |
| 9 | 硬件 | 内置摄像头(不声明 remote wakeup) | usb:3-1.1 | deny | —(设备不声明 remote wakeup) |
| 10 | 硬件 | CS9711 指纹(不声明 remote wakeup) | usb:3-1.3 | deny | —(设备不声明 remote wakeup) |
| 11 | 硬件 | 触摸板 / 触摸屏 | i2c:i2c-GXTP7385:00 | deny | 禁止(disabled) |
| 12 | 硬件 | 电池事件 | acpi:PNP0C0A:00 | deny | 禁止(disabled) |
| 13 | 硬件 | 电源键(ACPI 固定事件,未必真能屏蔽) | acpi:PNP0C0C:00 | deny | 禁止(disabled) |
| 14 | 硬件 | ACPI 睡眠键 | acpi:PNP0C0E:00 | deny | 禁止(disabled) |
| 15 | 硬件 | 交流适配器事件(拔插电) | ps:ADP1 | deny | 禁止(disabled) |
| 16 | 硬件 | HID 设备电池事件(键鼠电量) | hidbatt: | deny | 禁止(disabled) |
| 17 | 硬件 | 无线网卡根端口 | pci:0000:00:02.2 | deny | 禁止(disabled) |
| 18 | 硬件 | 读卡器根端口 | pci:0000:00:02.3 | deny | 禁止(disabled) |
| 19 | 硬件 | 空根端口 | pci:0000:00:04.1 | deny | 禁止(disabled) |
| 20 | 硬件 | xHCI1(摄像头 + 指纹所在控制器) | pci:0000:65:00.4 | deny | 禁止(disabled) |
| 21 | 硬件 | 雷雳 NHI | pci:0000:67:00.6 | deny | 禁止(disabled) |
| 22 | 硬件 | 雷雳域(热插拔唤醒) | tb:domain0 | deny | 禁止(disabled) |
| 23 | 硬件 | 雷雳根设备(据点 0-0) | tb:0-0 | deny | 禁止(disabled) |
| 24 | 计时 | RTC 闹钟(定时唤醒) | platform:PNP0B00:00 | deny | 禁止(disabled) |
| 25 | 计时 | alarmtimer(事件计时) | platform:alarmtimer.0.auto | deny | 禁止(disabled) |
| 26 | 定时 | systemd 定时器 WakeSystem(总闸,核验项) | check:timers-wakesystem | deny | 合规(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 语义、清单文件位置)仍在用户人工梳理中,本笔记只记录当前实现与分析结论。