summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-08-29 17:55:58 +0800
committerSomhairle H. Marisol <[email protected]>2026-08-29 17:55:58 +0800
commita6331995a8c7a100ce8865cd8aa70c116157e600 (patch)
tree08aa68e4c48a7b28d45b2c1b0ac5134e76b1d7ee /source
parent810ca18bd28fe958194021d4165c9564dcf69f3b (diff)
downloadblog-a6331995a8c7a100ce8865cd8aa70c116157e600.tar.gz
修订博文:修正'故障'定性 + 补全关键脚本完整源码(验证脚本、运行时脚本、服务单元)
Diffstat (limited to 'source')
-rw-r--r--source/_posts/给一台Linux笔记本做服务器化改造:休眠、充电上限与EC寄存器考古.md82
1 files changed, 74 insertions, 8 deletions
diff --git a/source/_posts/给一台Linux笔记本做服务器化改造:休眠、充电上限与EC寄存器考古.md b/source/_posts/给一台Linux笔记本做服务器化改造:休眠、充电上限与EC寄存器考古.md
index cb3d7f7f..3236421b 100644
--- a/source/_posts/给一台Linux笔记本做服务器化改造:休眠、充电上限与EC寄存器考古.md
+++ b/source/_posts/给一台Linux笔记本做服务器化改造:休眠、充电上限与EC寄存器考古.md
@@ -3,9 +3,9 @@ date: 2026-08-29 15:30:00
tags: [Technique, Linux]
---
-一台退役笔记本装载 Hermes Agent 后转为 7×24 常驻服务器,出厂的笔记本形态留下三类故障:拔电半小时后自动休眠、合盖即失联、电池持续充至满电。本文记录改造全程。前两项属常规配置;第三项在 Linux 下无现成驱动,需要从主板固件代码中定位充电上限的寄存器地址并写入硬件,构成本文主体。
+一台退役笔记本装载 Hermes Agent 后转为 7×24 常驻服务器,笔记本的默认行为与服务器角色有三处冲突:电源管理在拔电半小时后自动休眠、合盖即失联、充电不设上限。这些是厂商为移动场景设计的合理默认值,对服务器却是故障。本文记录改造全程。前两项属常规配置;第三项在 Linux 下无现成驱动,需要从主板固件代码中定位充电上限的寄存器地址并写入硬件,构成本文主体。
-改造对象为 Redmi Book Pro 15 2022,系统 Pop!_OS 24.04,内核 6.18.7。关键步骤均附可复核证据,脚本清单见文末。
+改造对象为 Redmi Book Pro 15 2022,系统 Pop!_OS 24.04,内核 6.18.7。所有脚本完整源码收录于正文。
## 自动休眠由两套机制叠加控制,仅修改其一必然复发
@@ -16,7 +16,7 @@ gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-typ
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing'
```
-第二处位于 systemd-logind。不处理它,合盖仍触发 suspend。向 `/etc/systemd/logind.conf.d/` 写入 drop-in 配置:
+第二处位于 systemd-logind。不处理它,合盖仍触发 suspend。向 `/etc/systemd/logind.conf.d/99-server-nosuspend.conf` 写入 drop-in 配置:
```ini
[Login]
@@ -29,6 +29,8 @@ AllowHibernation=no
`AllowSuspend=no` 与 `AllowHibernation=no` 使 suspend、hibernate 两个 systemd 目标在系统层不可达:任何组件发起休眠请求,systemd 直接拒绝执行。这一层保证无论用户会话如何变更,服务器不会被睡掉。
+上述配置对应脚本 `setup-server-power.sh`,其主体即本节三条命令加上 logind 重启,此处不再展开。
+
## 充电上限配置存在两年,从未生效
机器长期安装 TLP,配置如下:
@@ -40,7 +42,7 @@ STOP_CHARGE_THRESH_BAT0=80
电量仍充至 94%(衰减后的实际满充点)。TLP 自身不操作硬件,仅调用内核接口。内核标准接口为 `/sys/class/power_supply/BAT0/charge_control_end_threshold`;TLP 依次探测三种后端:`natacpi`(内核 ACPI 驱动直写)、`acpi_call`(第三方模块调用任意 ACPI 方法)、`sysfs`(上述标准接口)。三种后端在本机全部缺失:Redmi 未向内核提交任何 EC 驱动,`charge_control_end_threshold` 不存在,`acpi_call` 未安装。
-TLP 对后端缺失的处理是静默降级:保留配置,不执行任何操作。此即"设置了上限却充到满"的完整成因。
+TLP 对后端缺失的处理是静默降级:保留配置,不执行任何操作。此即"设置了上限却充到满"的完整成因。补装 `acpi-call-dkms` 后重启 TLP,`tlp-stat` 报告 `natacpi (system76_acpi) = inactive (laptop not supported)`,阈值接口依旧未出现——ACPI 层无方法可调,此路不通。
## 从 DSDT 定位 EC 充电上限字段
@@ -75,7 +77,31 @@ BATM, 16,
依据 ACPI Field 的位累积规则逐字段推算,各字段在 EC RAM 中的字节位置随之确定:电量百分比 RSOC 位于偏移 0x92,充电上限 BTCC 位于 0x95-0x96(16 位小端)。本机 EC RAM 经内存映射窗口 `0xFE0B0400` 可直接访问。
-偏移推算须经读取验证。一次读出 32 字节,与系统已知值交叉比对:
+偏移推算须经读取验证。验证脚本用 `dd` 从 `/dev/mem` 读出窗口内 32 字节,再按偏移表逐字段解码:
+
+```bash
+#!/bin/bash
+# ec-verify-readonly.sh — 只读验证偏移表,不做任何写入
+[ "$(id -u)" -eq 0 ] || { echo "请用 sudo 运行"; exit 1; }
+dd if=/dev/mem bs=1 skip=$((0xFE0B0480)) count=32 of=/tmp/ec-read.bin status=none
+python3 - <<'PYEOF'
+data = open("/tmp/ec-read.bin","rb").read()
+def u8(o): return data[o]
+def u16(o): return data[o] | (data[o+1] << 8)
+b80 = data[0]
+print(f"ACIN(AC插电) = {b80 & 1} (期望 1)")
+print(f"BTDC 0x84 = {u16(4)} mAh (对照 energy_full_design/电压)")
+print(f"BTDV 0x86 = {u16(6)} mV (期望约 15440)")
+print(f"BTFC 0x88 = {u16(8)} mAh (对照 energy_full)")
+print(f"BTPR 0x8E = {u16(14)} mAh (对照当前容量)")
+print(f"BTVT 0x90 = {u16(16)} mV (期望 16000-16800)")
+print(f"RSOC 0x92 = {u8(18)}% (对照 upower 实时值)")
+print(f"BTCC 0x95 = {u16(21)}% (充电上限,验证目标)")
+print(f"BTCT 0x8C = {u16(12)} 次 (循环次数)")
+PYEOF
+```
+
+一次读出 32 字节,与系统已知值交叉比对:
| EC 字段 | 读数 | 系统对照值 | 结果 |
|---|---|---|---|
@@ -91,9 +117,49 @@ BATM, 16,
## 写入与开机固化
-写入是针对内存映射窗口的 2 字节操作:`dd` 定位 `0xFE0B0495`,写入小端编码的 80,读回确认。行为验证给出更强的证据:写入时电量 87%,高于上限,电池状态立即转为 Discharging;电量自然回落至 83% 后状态转为 Charging。EC 严格执行"低于 80 充电、达到 80 停充"。
+写入是针对内存映射窗口的 2 字节操作。写入时电量 87%,高于上限,电池状态立即转为 Discharging;电量自然回落至 83% 后状态转为 Charging。EC 严格执行"低于 80 充电、达到 80 停充"。
+
+EC 值可能在冷启动后复位,需开机重写。方案为 oneshot 型 systemd 服务,执行 18 行运行时脚本 `/usr/local/bin/ec-charge-limit`——这是全套配置中唯一长期驻留的代码,全文如下:
+
+```bash
+#!/bin/bash
+# ec-charge-limit — BTCC @ 0xFE0B0495 (little-endian u16)
+# 用法: ec-charge-limit [20-100],由 ec-charge-limit.service 开机调用
+LIMIT=${1:-80}
+[ "$LIMIT" -ge 20 ] && [ "$LIMIT" -le 100 ] || exit 1
+BASE=$((0xFE0B0400)); OFF=$((0x95))
+[ "$(id -u)" -eq 0 ] || exit 1
+for i in 1 2 3 4 5; do
+ printf "$(printf '\\x%02x\\x00' "$LIMIT")" \
+ | dd of=/dev/mem bs=1 seek=$((BASE + OFF)) count=2 conv=notrunc status=none \
+ || { sleep 2; continue; }
+ sleep 1
+ dd if=/dev/mem bs=1 skip=$((BASE + OFF)) count=2 of=/tmp/.btcc-check status=none
+ V=$(python3 -c "d=open('/tmp/.btcc-check','rb').read(); print(d[0]|(d[1]<<8))")
+ [ "$V" = "$LIMIT" ] && { logger -t ec-charge-limit "BTCC set to $LIMIT (attempt $i)"; exit 0; }
+ sleep 2
+done
+logger -t ec-charge-limit "FAILED to set BTCC=$LIMIT after 5 attempts"
+exit 1
+```
+
+脚本包含三重保护:参数限制在 20-100;写入后读回比对,不匹配则重试;至多 5 次,失败写 syslog 并以非零码退出。服务单元 5 行:
+
+```ini
+[Unit]
+Description=Set Redmi Book Pro charge limit to 80% via EC memory window
+After=multi-user.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/local/bin/ec-charge-limit 80
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
+```
-EC 值可能在冷启动后复位,需开机重写。方案为 oneshot 型 systemd 服务(`WantedBy=multi-user.target`),执行 20 行 shell 脚本:写入、至多 5 次读回重试、结果写 syslog。验证一行命令:`journalctl -t ec-charge-limit`。
+部署后验证:`journalctl -t ec-charge-limit` 应含 `BTCC set to 80 (attempt 1)`。
## 方法论:固件代码是最后的文档
@@ -108,4 +174,4 @@ TLP 配置存在而从未生效,根源在于用户态工具依赖内核 ABI �
- 开机自动重写上限,冷启动不丢配置
- 电池档案:设计 72 Wh,满充 60.8 Wh(健康度 84.5%),循环 1196 次
-脚本归档:`setup-server-power.sh`(休眠禁用)、`acpi-probe-battery.sh`(DSDT 反汇编与只读探测)、`ec-verify-readonly.sh`(字段交叉验证)、`ec-set-charge-limit.sh`(写入与行为观察)、`power-rollback.sh`(一键回滚)。其他"EC 沉默"的机型可直接复用该流程:先以只读交叉验证证明地址表,再执行写入。
+辅助脚本归档于本地(篇幅所限未全文收录):`setup-server-power.sh`(休眠禁用,含备份与逐步回显)、`acpi-probe-battery.sh`(DSDT 反汇编与 ACPI 只读探测)、`ec-set-charge-limit.sh`(首次写入与 60 秒行为观察)、`power-rollback.sh`(一键回滚)。其他"EC 沉默"的机型可直接复用核心流程:反汇编 DSDT 检索字段表 → 手工推算偏移 → 只读交叉验证 → 验证命中后再写入。