再次失败,nixos的睡眠太随缘了
问题现象
- 拔电合盖后顺利睡眠,6小时后被唤醒,然后每60秒系统睡眠并系统唤醒,无限循环
- 拔电合盖后顺利睡眠,4小时后被唤醒,然后每60秒系统睡眠并系统唤醒,无限循环
- hermes+deepseek排查没有任何线索,我只能求助deepseek chat,它的搜索能力特别强
deepseek chat提供线索
你遇到的这个“睡20秒醒60秒”的循环,在NixOS社区里并非孤例,而且确实有迹可循。**核心问题通常是某个硬件或驱动在系统进入睡眠后,不断地发出唤醒信号(Wakeup Event),导致系统刚睡下就被立刻叫醒,然后 logind 等组件又尝试让它重新睡眠,从而形成循环。**
### 🔍 第一步:找到“元凶”
盲目修改配置效率很低,建议先通过日志精确定位唤醒源。你的场景(合盖、拔电、无扩展)和社区一个详细案例高度吻合。
1. **复现后立即抓取日志**:让电脑复现循环后,唤醒它,然后立即执行:
```bash
journalctl -b -u systemd-suspend --no-pager
```
查看每次睡眠和唤醒的精确时间点。
2. **检查 ACPI 唤醒源**:这是最关键的线索。运行:
```bash
cat /proc/acpi/wakeup
```
关注 `Status` 为 `*enabled` 的设备。社区案例中,**`GPE 0x0B`** 在2.5小时内触发了660次,占SCI中断的95%,是典型的“元凶”。
3. **检查内核唤醒中断**:
```bash
cat /sys/power/pm_wakeup_irq
```
社区案例显示该值为 **`7` (pinctrl_amd)**,指向了AMD平台的引脚控制器。
4. **检查 USB 设备**:你的场景是“无扩展”,但笔记本内置的USB设备(如摄像头、蓝牙、指纹识别器)仍可能捣乱。社区案例中指纹设备`usb 3-1.3`反复断开/重连了158次,是循环的驱动因素之一。
### 🛠️ 第二步:针对性解决方案
#### 方案一:禁用特定设备的唤醒能力(最推荐)
找到元凶后,用 `udev` 规则永久禁用它。例如,如果发现是某个PCI设备(如NVMe、USB控制器),可以获取其 `vendor` 和 `device` ID:
```bash
udevadm info -a /sys/bus/pci/devices/<你的设备地址> | grep -E 'ATTR{vendor}|ATTR{device}'
```
然后在 `configuration.nix` 中添加:
```nix
services.udev.extraRules = ''
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x你的ID", ATTR{device}=="0x你的ID", ATTR{power/wakeup}="disabled"
'';
```
如果是USB设备,可将 `SUBSYSTEM=="pci"` 改为 `SUBSYSTEM=="usb"`。
#### 方案二:禁用所有 PCIe 唤醒(简单粗暴)
如果你不需要通过网络唤醒或外接PCIe设备唤醒电脑,可以一刀切禁用所有PCIe端口的唤醒能力:
```nix
services.udev.extraRules = ''
ACTION=="add", SUBSYSTEM=="pci", DRIVER=="pcieport", ATTR{power/wakeup}="disabled"
'';
```
这能解决大部分因NVMe或PCIe控制器引起的异常唤醒。
#### 方案三:调整 NVIDIA 电源管理(如果有NVIDIA显卡)
如果你的笔记本有NVIDIA独显,其电源管理功能是已知的“睡眠循环”常见诱因。在 `configuration.nix` 中尝试:
```nix
hardware.nvidia.powerManagement.enable = false;
```
社区中多位用户确认这能解决 `suspend/resume cycles`。
#### 方案四:检查 logind 与合盖配置
确认合盖行为未被意外覆盖。检查 `configuration.nix` 中是否包含:
```nix
services.logind.extraConfig = ''
HandleLidSwitch=suspend
HandleLidSwitchExternalPower=suspend # 拔电时也睡眠
'';
```
同时确保没有设置 `HandleLidSwitch=ignore` 导致系统行为异常。
#### 方案五:排查 `suspend-then-hibernate` 陷阱
如果你配置了“睡眠一段时间后休眠”,但**交换分区(swap)未正确配置**(例如使用了加密swap但未正确设置),系统会在 `HibernateDelaySec` 超时后尝试休眠失败,然后直接唤醒,造成循环。检查 `/etc/systemd/sleep.conf` 或 NixOS 选项 `systemd.sleep.extraConfig`,看是否设置了不切实际的超时。
### 💡 排查建议
建议先从**方案三**(NVIDIA)和**方案五**(sleep配置)查起,因为它们配置简单且常见。如果无效,再通过第一步的日志锁定具体设备,应用**方案一**进行精准打击。修改配置后记得 `sudo nixos-rebuild switch` 并**完全重启**一次,以确保 `udev` 规则和内核参数生效。hermes被提醒后的结论
A 睡眠成功(正常)
23:15:48→06:16:37 真睡 7 小时:监管日志 7 小时零 monitor 行,电量 100%→95%(0.7%/h)。真睡签名=墙钟 25249.0s/内核单调 1.4s。06:16:37 那批 Freezing 行是 monotonic 跨睡眠的假时间戳,不是又睡了一次。
B 唤醒(不正常)
唤醒记账=ACPI 电源键设备 PNP0C0C(\_SB_.PWRB):active/event 298、wakeup 286,与全机 suspend 298 次 1:1,醒着时零增长。盖子 PNP0C0D 仅 5 次、xhci/WiFi wakeup 全 disabled、指纹无 wakeup 节点;irq7=pinctrl_amd、GPE 0x0B 唯一活跃。
【更正1】「禁 PWRB wakeup 不可行」→ 错:sysfs 确有 /sys/devices/platform/PNP0C0C:00/power/wakeup。
【更正2】「内置键盘本来就叫不醒」→ 错:i8042 wakeup=disabled 是内核默认,只表示内核没把 PS/2 键盘登记为独立唤醒源。
【更正3·DSDT 实证】PWRB 根本不是唤醒源,见下节。
C 翻饼(不正常)
从未睡。一轮按内核单调:entry→sync 0.007s→1.48s→freeze 0.002s→EC blocked→unblocked 6ms→exit,全程 1.47s;墙钟却显示 19.7s。差的 18.2s 是墙钟跳变——80 次 Clock change detected、74 轮共跳 1535s≈25.6 分钟(事后已校正回准)。流产=进 s2idle 6ms 内被待处理唤醒打回。真实周期 1.5s 尝试+41.4s 等待=43s。本机 mem_sleep 只有 [s2idle],无 S3 可退。三次循环终止时刻全部是开盖(11-06 09:31:45 / 11-09 08:13:44 / 11-10 07:31:18)。
D 日志失真(已修,世代 powermanager-duration-verdict)
wake 行改为带时长与判定:真睡 `(墙钟25249.0s 单调1.4s 判定=真睡)`,流产 `(墙钟19.7s 单调1.5s 判定=流产未睡)`;阈值 60s。睡眠行 mode/电量改取上轮活值。DSDT 反编译:唤醒链路的固件真相
取证:sudo cp /sys/firmware/acpi/tables/DSDT . && iasl -d DSDT.aml → 312,042 字节 ASL
1. PWRB 不是唤醒源,是固件伪造的通知接收者
Device (PWRB):只有 _HID(PNP0C0C)/_UID(0xAA)/_STA,**没有 _PRW**
全 DSDT 只有 2 处 GPRW:GPP0._PRW → GPRW(0x08,0x04);LID0.RHRW → GPRW(0x0E,0x03)(RHRW 无人调用)
Method (_WAK,1) → SWAK(Arg0) → If(Arg0==3||4) Notify(\_SB.PWRB, 0x02) // Device Wake
⇒ 286 次 PWRB 记账 = 每次 resume 的固定产物,不是 286 次按键
2. 真入口是 EC 的 SCI(Scope(_GPE) 内无 _Lxx/_Exx → GPE 由内核 EC 驱动接管)
EC0(PNP0C09) 事件表:_Q0A 电源插拔、_Q0B 电池、_Q0C/_Q0D 盖子、_Q54 电源键、_Q58 温度、_Q74-76 显示
每个 _Qxx 往 P80H 写特征码(0xEC0A/0xEC0B/0xEC0C/0xEC0D/0xEC54)
3. 盖子/电源键/键盘全走 EC:_LID = EC0.LIDS & 1(读 EC RAM 位)
ec_no_wakeup=1 源码行为:acpi_ec_suspend_noirq() → acpi_set_gpe(EC_GPE, ACPI_GPE_DISABLE),
resume_noirq 再 ENABLE(ec.c 2116-2141)→ 睡眠期间 EC 事件全进不来
⇒ 开盖/电源键/键盘唤醒全失效 → 本机 ec_no_wakeup 与 acpi_mask_gpe 不可用
4. 「模拟合盖」OS 层无等价物:盖子状态是 EC RAM 位 + EC 物理输入,不能伪造社区与上游:G1619-04 是已知缺陷
1. 内核有专门针对本机型的补丁(已生效)
commit 805c74eac8cb「gpiolib: acpi: Ignore touchpad wakeup on GPD G1619-04」
(2024-01-17,Mario Limonciello @ AMD,Cc stable)
"Spurious wakeups are reported on the GPD G1619-04 which can be absolved by
programming the GPIO to ignore wakeups."
DMI_MATCH(SYS_VENDOR,"GPD") + DMI_MATCH(PRODUCT_NAME,"G1619-04"),.ignore_wake="PNP0C50:00@8"
注释:Spurious wakeups from TP_ATTN# pin / Found in BIOS 0.35
本机验证:pin #8(DSDT TPDD 的 GpioInt pin 0x0008)有中断但 S0i3/S3/S4S5 唤醒位全空
(对照 pin #0 三个全亮)→ quirk 生效,触摸板这一路已被内核压掉
2. 上游 issue 就是这台机器
AMD DRM #3073「GPD win max 2 AMD 7840u s2idle suspend inconsistency + suspend power drain
+ frequency stuck at 544mhz」(2023-12-24 开,2024-01-28 关,labels: Phoenix/Hawk Point, s0ix)
其 s2idle.py 报告与本机逐项对上:
Woke up from IRQ 7 (IR-IO-APIC 7-fasteoi pinctrl_amd) ← 本机 pm_wakeup_irq=7
gpe0B increased from 0 to 2 ← 本机唯一活跃 GPE 0x0B
In a hardware sleep state for 0:11:29 (17.38%) ← 本机 74 轮真睡 0 次
power drain nearly 5-10W (10-15%/h) ← 本机 9%/h
还有「cpu fan still working even on suspend」「wake up randomly by itself」
该机主 BIOS 0.35 / EC 0.14;本机 BIOS 0.41
3. 社区 workaround(aarron-lee/gpd-win-tricks,GPD 掌机 Linux 事实标准仓库)
wm2-suspend-udev.sh:udev 置 i2c PNP0C50:00 的 power/wakeup=disabled(quirk 的用户态版)
指纹必须彻底禁:SUBSYSTEM=="usb", ATTR{idVendor}=="2541", ATTR{idProduct}=="9711", ATTR{remove}="1"
← 正是本机 3-1.3(2541:9711),每次 resume 重枚举 ~19 行的那个
wm2-wakeup-workaround:检测盖子仍合着就再 suspend 一次(用户态重试=治循环)
README 原文:「the GPD WM2 (7840u, 8840u) has issues where it will wake up periodically」
原始出处 SpookOz/spook-wm2
同仓库另有 win4-suspend-mods、fuzzy-sound-suspend-fix、win2 等
4. nixos-hardware 有 gpd/win-max-2 profile(default.nix 只 import laptop/ssd/hidpi/ppt,无睡眠设置)
5. 本机 s2idle 期间仍 armed 的针脚(quirk 之后剩下的嫌疑)
看 S0i3/S3/S4S5 列(不是 wake 能力列):
pin #0 S0i3+S3+S4S5 全亮(edge/ActiveHigh/debounce 46875us,claimed)
pin #58 S0i3+S4S5 pin #59 S0i3+S4S5 pin #61 S0i3+S3 pin #62 S0i3+S3
pin #52/#54 仅 S3
AMD 报告里「GPIOs active: ['0','58']」也正好是 0 和 58 → 这两个是 s2idle 活跃针
要压只能靠内核 quirk(boot.kernelPatches 扩 ignore_wake 列表,需精确 device@index),高风险,未做
结论:不是「NixOS 在笔记本上睡不了」,而是 G1619-04 这块板子的 s2idle 唤醒缺陷;
触摸板那一路上游已修(本机已生效),剩余 0/58/59/61/62 是候选。hermes修复方案实施
【一、已实施·D 日志失真】/etc/nixos/modules/system/service/power-battery-sleep.py
1. sleep_wake_events() 改 journalctl -o json,同时取墙钟与内核单调,配对算 dw/dm
2. 判定 est=dw-dm:≥60s「真睡」,否则「流产未睡」
3. wake 行追加 `(墙钟Xs 单调Ys 判定=…)`
4. sleep 行 mode/电量取上轮活值,删 upower_at()
5. 关联块移到 cond 之前;reported_ev 防重复
落地:世代 …10-39-21-…-duration-verdict(rv8xgpbv…)、提交 9dfc38e
【二、已实施·配套取证】wakeup_line() 唤醒陷阱、打摆子判定、每样本必记 monitor 行
提交:6c9d717、363e905
【三、已实施·治循环】提交 05797a0,世代 …14-36-20-…-flap-inhibitor(2hj74xaq…)
- 修回归:sleep_wake_events 改 4 元组后首圈仍 3 元组解包(一有事件即崩),已改整元组
- 单轮采样内「判定=流产未睡」≥3 次 且 DC+合盖 → systemd-run 起 pbs-sleep-hold
→ systemd-inhibit --what=sleep --mode=block 持锁 1800s → logind/PowerDevil 的 sleep 被拒
- 条件解除立即解锁;抑制期间本监管不再自己发 sleep 信号
- 实测:arm→active、inhibit 清单可见、release→inactive;历史窗回放 151 事件/74 流产/1 真睡
- 收益如实记:翻饼 43s 周期里只有 1.5s 在尝试睡,97% 时间本来醒着 → 停手几乎不省电,
买到的是止损(墙钟不再每小时被拨 25.6min、3-1.3 不再每 43s 重枚举、日志干净)
【四、已实施·作业法】绕开:AC 合盖入睡 → 睡着后再拔电(已证 11-09 真睡 7h / 0.7%h)
【五、未实施·候选】
- 指纹 udev remove=1(社区明确要求,本机 3-1.3 每次 resume 重枚举)
- 内核 quirk 扩 ignore_wake 到 pin 0/58/59/61/62(高风险,需精确 device@index)
- GPD BIOS/EC 更新(本机 0.41;官网 429 限流未查到)
【六、回滚】git revert 9dfc38e / 05797a0 → rebuild;世代 --rollback;
日志删 power-battery-sleep-state/{log,state.json,force.lock} 复位