电池改dcdc直供后
电量还是会一直下滑,到低电量的时候,驱动层会降频等。
原因
电量它是计算电池端耗电多少,高通傻逼pm芯片,不看电压。。。。
怎么办怎么办?
欺骗系统,直接改驱动层,不想重新编译系统,直接HOOK驱动劫持!!!!
开干!!!
现在是0:52了,群主也没睡,我也不能睡。
技术说明
是的,Linux内核模块可以修改其他驱动的寄存器值,原理如下:
为什么可以这样做
Linux内核是单一地址空间
↓
所有内核代码(包括模块)共享同一个内核虚拟地址空间
↓
任何内核模块都可以访问内核中的任何数据结构和函数
↓
没有进程间隔离,没有权限边界这和用户空间完全不同:
用户空间进程A 内核空间
↓ ↓
虚拟地址空间A 所有驱动共享
无法访问进程B VS 模块A可以直接
的内存 访问驱动B的数据我们做了什么
Hook①:替换函数指针
/* power_supply_desc 是个普通的内核结构体 */
struct power_supply_desc {
const char *name;
int (*get_property)(...); /* ← 函数指针 */
...
};
/* 我们直接修改这个指针 */
orig_desc = bat_psy->desc; /* 保存原始指针 */
fake_desc.get_property = fake_fn; /* 替换为我们的函数 */
bat_psy->desc = &fake_desc; /* 写入结构体 */这就像在C语言里修改一个全局变量,完全合法。
Hook②:通过regmap写寄存器
/* regmap是内核提供的寄存器访问抽象层 */
regmap_write(chg_regmap, 0x1061, fake_soc);regmap_write()
↓
regmap核心层(内核)
↓
SPMI总线驱动
↓
实际写入PMIC硬件寄存器regmap是公开的内核API,任何模块都可以调用,前提是拿到regmap句柄。
完整链路
我们的模块
↓
bus_find_device_by_name() ← 在内核设备树里找到charger设备
↓
dev_get_regmap() ← 获取该设备的regmap句柄
↓
regmap_write(map, 0x1061, 95) ← 通过regmap写PMIC寄存器
↓
SPMI总线 ← 串行电源管理接口
↓
PM8150B PMIC芯片 ← 物理写入寄存器为什么0x4009写不进去但0x1061可以
0x4009 — FG芯片SOC寄存器
↓
由FG硬件自主维护
CPU写入后硬件立即覆盖
属于"硬件只读"
0x1061 — Charger驱动SOC缓存
↓
由charger驱动软件维护
CPU写入后保持,直到驱动下次更新
属于"软件可写"安全性说明
这种做法在生产系统中属于hack,存在以下风险:
| 风险 | 说明 |
|---|---|
| 内核稳定性 | 修改其他驱动数据可能导致竞争条件 |
| 内核升级 | 结构体布局变化导致模块失效或崩溃 |
| 模块签名 | 生产内核要求模块签名,out-of-tree模块会taint内核 |
| 并发访问 | 未加锁的结构体修改可能在SMP下出问题 |
但在这个场景下(DCDC供电的嵌入式设备,固定内核版本),完全可以接受。
通过逐步缩小范围的方法找到的,整个过程是:
第一步:找到设备节点
# 从已知的电源管理路径出发
ls /sys/class/power_supply/
# → pm8150b-charger qcom-battery tcpm-...
# 找到FG设备在哪个SPMI总线上
ls /sys/bus/spmi/devices/0-02/
# → c440000.spmi:pmic@2:fuel-gauge@4000 ← FG设备
# → c440000.spmi:pmic@2:charger@1000 ← Charger设备第二步:找到regmap调试接口
# debugfs暴露了所有regmap设备的寄存器
ls /sys/kernel/debug/regmap/
# → 0-02 ← pmic@2,包含FG和Charger
# 可以直接读取所有寄存器的值
cat /sys/kernel/debug/regmap/0-02/registers第三步:用diff定位SOC寄存器
这是最关键的方法,等电量变化1%,对比前后寄存器差异:
# 保存77%时的快照
cat /sys/kernel/debug/regmap/0-02/registers > /tmp/reg_77.txt
# 等电量降到76%,保存快照
cat /sys/kernel/debug/regmap/0-02/registers > /tmp/reg_76.txt
# 对比差异
diff /tmp/reg_77.txt /tmp/reg_76.txtdiff结果中跟随电量变化的寄存器就是SOC寄存器:
< 4009: c5 ← 77%时
> 4009: c3 ← 76%时 ← 差了2,对应1%
< 400a: c5
> 400a: c3 ← 同步变化,备份寄存器第四步:用grep找charger SOC寄存器
# 已知电量78%=0x4e,搜索所有值为0x4e的寄存器
cat /sys/kernel/debug/regmap/0-02/registers | \
awk -F': ' '$2=="4e"'
# 结果
1061: 4e ← 在charger区域(0x1000~0x1fff)第五步:确认0x1061是charger读取的
通过地址范围推断:
PMIC寄存器地址空间分配:
0x4000~0x4fff → FG(fuel-gauge@4000)
0x1000~0x1fff → Charger(charger@1000)
0x1061 在charger地址空间内
↓
必然是charger驱动读取的SOC缓存第六步:验证0x1061可写
# 通过内核模块写入,读回验证
regmap_write(chg_regmap, 0x1061, 95);
regmap_read(chg_regmap, 0x1061, &cur);
# 读回=95 → 可写 ✅整个方法论总结
已知现象(电量下降)
↓
找到相关设备节点(/sys/class/power_supply)
↓
找到debugfs寄存器接口(/sys/kernel/debug/regmap)
↓
diff快照法定位变化寄存器(等1%变化)
↓
grep法搜索特定值(搜索0x4e=78%)
↓
地址范围推断归属驱动(0x1000=charger)
↓
内核模块验证可写性
↓
确认方案可行本质上是黑盒逆向,不依赖任何高通内部文档,完全通过Linux内核暴露的调试接口推断出来的。
FG SOC Hook — 高通PM8150B电量伪造技术文档
背景
小米K20 Pro(Snapdragon 865)拆除电池后使用DCDC模块固定4.2V供电,
系统燃油计(Fuel Gauge)通过库仑计累积放电电流,导致电量持续下降,
最终触发系统关机保护。本方案通过内核模块实现双重hook,将电量固定为95%。
一、原理
1.1 问题根源
DCDC 4.2V 固定供电
↓
PM8150B FG 燃油计
├── 电压法:4.2V → 约90%(开机时读一次)
└── 库仑计:持续累积 current_now(负值)
↓
电量 = 初始值 - 累积放电量
↓
电量持续下降,最终触发关机1.2 尝试过的方案(均失败)
| 方案 | 失败原因 |
|---|---|
| 写 capacity sysfs | 只读节点 |
| 写 pm8150b-charger/status | 被驱动覆盖,电量仍下降 |
| FG驱动 unbind/bind | FG从PMIC SRAM恢复旧值 |
| 写 0x4009 SOC寄存器 | 硬件只读,FG芯片自主更新 |
| 写 0x41a0 CC_SOC寄存器 | FG芯片立即覆盖 |
| kprobe hook | 内核禁用了kprobe(EOPNOTSUPP) |
1.3 成功方案:双重Hook
系统中有两套独立的电量读取路径:
FG芯片硬件
↓
├─→ qcom-fg驱动 → power_supply子系统
│ ↓
│ Hook ①:替换get_property函数指针
│ ↓
│ 用户空间/脚本看到95% ✅
│
└─→ qcom-smbx-charger驱动
直接读0x1061寄存器
↓
Hook ②:定时器每300秒写入fake值
↓
低电量保护看到95% ✅Hook ① — power_supply描述符替换
Linux power_supply子系统通过 power_supply_desc.get_property 函数指针
向上层提供数据。内核模块替换该指针为自定义函数,在返回时强制将POWER_SUPPLY_PROP_CAPACITY 替换为 fake_soc。
Hook ② — charger寄存器定时写入
charger驱动(qcom-smbx-charger)直接读取PMIC寄存器 0x1061 获取SOC,
用于低电量关机保护决策,完全绕过power_supply子系统,因此需要独立处理。
通过内核定时器每300秒向 0x1061 写入fake值,覆盖真实电量。
1.4 为什么选择95%而不是100%
充电IC检测到100%会触发停充逻辑,关闭USB供电路径(本设备曾出现
充满后USB断电问题)。95%让系统认为仍在充电中,USB供电路径保持开启。
1.5 关键寄存器
| 寄存器 | 地址 | 说明 |
|---|---|---|
| FG SOC | 0x4009 | FG芯片自主维护,硬件只读 |
| FG SOC备份 | 0x400a | 同上 |
| Charger SOC | 0x1061 | charger驱动读取,可写 |
| CC_SOC | 0x41a0~0x41a3 | 库仑计累积值,硬件只读 |
二、编译环境搭建
2.1 环境要求
| 项目 | 要求 |
|---|---|
| 硬件 | 小米K20 Pro(SM8150/Snapdragon 865) |
| 系统 | Ubuntu 26.04 (aarch64) |
| 内核 | 7.0.0-sm8150-g03c45beed5d5 |
| 编译器 | clang(内核由clang编译,必须用clang) |
| 内核头文件 | linux-headers-7.0.0-sm8150-g03c45beed5d5 |
2.2 安装依赖
apt update
apt install -y clang make
# 确认内核头文件存在
ls /usr/src/linux-headers-$(uname -r)/
# 确认clang版本
clang --version
uname -r2.3 目录结构
/opt/app/fg_hook/
├── fg_hook.c # 内核模块源码
├── Makefile # 编译脚本
├── fg_hook.ko # 编译产物
└── install.sh # 一键安装脚本三、源代码
3.1 内核模块 fg_hook.c
见同目录 fg_hook.c 文件。
核心逻辑说明:
// Hook ①:替换power_supply描述符的get_property
fake_desc.get_property = fake_get_property;
bat_psy->desc = &fake_desc;
// Hook ②:定时器每300秒写charger SOC寄存器
regmap_write(chg_regmap, 0x1061, fake_soc);
mod_timer(&soc_timer, jiffies + HZ * 300);3.2 Makefile
obj-m += fg_hook.o
KDIR := /usr/src/linux-headers-$(shell uname -r)
PWD := $(shell pwd)
all:
make CC=clang -C $(KDIR) M=$(PWD) modules
clean:
make CC=clang -C $(KDIR) M=$(PWD) clean四、编译
mkdir -p /opt/app/fg_hook
cd /opt/app/fg_hook
# 将 fg_hook.c 和 Makefile 放入该目录后编译
make
# 编译成功输出
# CC [M] fg_hook.o
# MODPOST Module.symvers
# LD [M] fg_hook.ko五、部署
5.1 一键安装
chmod +x install.sh
./install.sh5.2 手动加载测试
# 加载(默认95%)
insmod /opt/app/fg_hook/fg_hook.ko fake_soc=95
# 验证power_supply hook
cat /sys/class/power_supply/qcom-battery/capacity
# 应输出:95
# 验证charger寄存器
cat /sys/kernel/debug/regmap/0-02/registers | grep "^1061:"
# 应输出:1061: 5f
# 查看内核日志
dmesg | grep fg_hook5.3 systemd 开机自启
cat > /etc/systemd/system/fg-hook.service << 'EOF'
[Unit]
Description=FG SOC Hook v2 - Fix battery percentage for DCDC power supply
After=multi-user.target
DefaultDependencies=no
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/insmod /opt/app/fg_hook/fg_hook.ko fake_soc=95
ExecStop=/sbin/rmmod fg_hook
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable fg-hook
systemctl start fg-hook
systemctl status fg-hook5.4 运行时修改电量值
# 不重载模块,直接修改参数,下次定时器触发时生效
echo 90 > /sys/module/fg_hook/parameters/fake_soc六、验证
# 验证电量固定
cat /sys/class/power_supply/qcom-battery/capacity
# 验证charger寄存器(0x5f = 95)
cat /sys/kernel/debug/regmap/0-02/registers | grep "^1061:"
# 查看hook日志
dmesg | grep fg_hook
# 查看定时器运行(300秒后出现)
dmesg | grep "定时写0x1061"
# 查看模块参数
cat /sys/module/fg_hook/parameters/fake_soc预期内核日志:
[ 24s] fg_hook: [1] power_supply hook完成
[ 24s] fg_hook: [2] charger regmap获取成功
[ 24s] fg_hook: [3] 0x1061写前=0x4e(78%)
[ 24s] fg_hook: [3] 0x1061写后=0x5f(95%)
[ 24s] fg_hook: [4] 定时器已启动(每300秒)
[ 24s] fg_hook: 全部完成 fake_soc=95%
[ 324s] fg_hook: 定时写0x1061,读回=0x5f(95%)七、注意事项
内核升级后需要重新编译
内核模块与内核版本强绑定,升级内核后必须重新编译:
cd /opt/app/fg_hook
make clean && make
systemctl restart fg-hook不影响实际供电
本模块只修改上报给软件层的电量数值,不修改FG硬件寄存器,
DCDC实际供电电压和电流不受影响。
卸载后恢复真实电量
systemctl stop fg-hook
# 电量恢复真实值,会重新开始下降定时器间隔说明
定时器300秒写一次0x1061,是为了应对charger驱动定期刷新该寄存器。
如发现电量被短暂恢复为真实值,可缩短间隔:
# 修改源码后重新编译
sed -i 's/HZ \* 300/HZ * 60/' /opt/app/fg_hook/fg_hook.c
make clean && make
systemctl restart fg-hook八、文件清单
/opt/app/fg_hook/
├── fg_hook.c 内核模块源码
├── Makefile 编译脚本
├── fg_hook.ko 编译产物(需重新编译)
└── install.sh 一键安装脚本
/etc/systemd/system/
└── fg-hook.service systemd服务配置RTC时间
有两个问题:
- 网络还没就绪 DNS 解析失败,服务启动太早了
- hwclock 路径不对,Ubuntu 26.04 路径变了
修复脚本
先安装 apt install ntpsec-ntpdate -y
sudo tee /usr/local/bin/force-timesync.sh > /dev/null <<'EOF'
#!/bin/bash
# 等待网络真正就绪
for i in $(seq 1 30); do
ping -c 1 -W 2 223.5.5.5 > /dev/null 2>&1 && break
sleep 2
done
# 强制同步时间
ntpdate ntp.aliyun.com || ntpdate 203.107.6.88 || ntpdate 223.5.5.5
# 修正 hwclock 路径
$(which hwclock) --systohc 2>/dev/null || true
# 重启 chrony
systemctl restart chrony
EOF
sudo chmod +x /usr/local/bin/force-timesync.sh修复 service,等待网络真正就绪
sudo tee /etc/systemd/system/force-timesync.service > /dev/null <<'EOF'
[Unit]
Description=Force NTP time sync on boot
After=network-online.target nss-lookup.target
Wants=network-online.target nss-lookup.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/force-timesync.sh
RemainAfterExit=yes
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target
EOF重新加载
sudo systemctl daemon-reload
sudo systemctl restart force-timesync.service
sudo systemctl enable force-timesync.service
journalctl -u force-timesync.service -f用 -f 实时查看日志,确认同步成功。


