Skip to content

Releases: aapplle/op13-battery-unlock

一加13解容 uv2800 v11

Choose a tag to compare

@github-actions github-actions released this 05 Oct 21:35

一加 13 解容模块 uv2800 v11

v11 是在 v10 基础上做的一次全面重构:内核模块、用户态脚本、状态文件和文档都重新整理过。
以下为两个改动最大、用户能直接感觉到的变化。

一、越狱模式下不用再插拔充电器

v10 走越狱路线时有一个躲不掉的动作:模块注入之后要插一下充电器再拔掉,否则模块拿不到电量计的设备指针,
也就等于没生效。这一步用户能感觉到,而且很容易漏做。

v11 去掉了这个动作,办法是模块自己制造一次驱动调用:

  • 模块往 deep_dischg_counts 写入同一个值(模块源目录/log.sh 里的 uv_capture_dev)。
  • 驱动收到这次写入,会调用 oplus_fg_set_batt_deep_dischg_count;模块在这个函数的入口挂了挂钩
    (内核源码/uv2800.c 里的 kp_ddrc),顺手把设备指针记下来。
  • 万一还是没拿到,模块源目录/action.sh 会再读一次 ADSP 确认;仍然拿不到就提示重启,
    不会拿猜出来的值往下走。

关机电压这一侧本来就跟插拔无关:挂钩直接改寄存器,vbat_uv 读到的就是设定值本身,改参数立刻跟随、
没有延迟。ADSP 终止电压则改由模块主动回写,不再等驱动来纠偏 —— 驱动只在"投票目标和当前值不一样"时
才调 setter,平时两者相同,被动拦截根本不会触发。

所以现在两种部署方式(标准启动、越狱注入)装完就能用,全程不碰充电器。

二、原厂截止电压改成完全复刻厂商算法

点「执行」按钮和卸载模块时,都要把电量计的终止电压恢复成这台设备当前的原厂值。这个值不是固定数字,
驱动是按"深度放电比率档 × 温度档"两个维度查设备树里的 ddrc_strategy 子树算出来的。

v10 用的是另一套算法:查 deep_spec,term_coeff 那张表,用原始计数去选行。那张表只决定驱动用哪张
ddrc 表,不是电压来源,按那套算出来的原厂值是错的。v11 通过进一步逆向,完整
复刻了原厂计算方式,具体实现在 模块源目录/log.sh 的 uv_dt_orig:

输入:counts = 深度放电计数,cc = 电池循环次数,
      cali = 深度放电计数校准值(节点不存在就当 0),temp = 电池温度(0.1 ℃)

① ratio  = 10 × counts / cc(只在 0 < cc < 5000 时做除法)
② region = ratio 依次和 oplus,ratio_range 比,大于等于就抬一档(0=min … 5=high)
③ index_t= temp 和 oplus,temp_range 比,小于等于就归下一档(0=cold … 3=warm)
④ 表     = <电池型号>/ddrc_strategy/strategy_ratio_range_<region>/strategy_temp_<温度>
           每行 4 个 32 位整数(大端):阈值、关机电压、终止电压、序号
⑤ 选行 k = 满足 max(0, 该行阈值 − cali) ≤ cc 的最大行号(一行都不满足就取第 0 行)
⑥ 结果   = 第 k 行的第 3 个字段

新增自检机制:算不出来就返回空,不会盲目写入。推算过程带完整自检 —— 输入必须是纯数字;计数或循环次数超过
一百万、校准值超过一万判失败;校准值节点不存在按 0 处理,但节点存在而值非法判失败;两个输入都是 0
判失败;ratio_range 必须正好 5 项、temp_range 必须正好 3 项;目标表字节数是 16 的倍数、行数在
1 到 8 之间;每行两个电压列都必须落在 2000–5000;第 0 行阈值必须是 0 且阈值单调不降;最终结果必须落在
2900–3500。任何一项不过就返回空字符串,调用方各自走安全路径(开机脚本打警告、手动恢复脚本退出、
卸载脚本跳过写入),不会拿猜测值回填。

三、其它变化

  • 关机电压默认 2800 mV,范围 2800–3060 mV;ADSP 终止电压按「关机电压 − max(0, 3060 − 关机电压)」派生,默认 2540 mV。
  • 六个参数接口:uv_target_mv、uv_adsp_mv、adsp_read、adsp_write、resume、adsp_debug。
  • 跨 ColorOS 版本靠两套 profile 按指令字自动选择;加载时会校验四条关键指令字,对不上就拒绝加载。
  • 越狱路线下的电量挂载走 PID 1 的全局命名空间,卸载时会解绑。
  • 给使用者看的说明在 README.md,技术细节在 项目文档.md,构建与上游来源在 THIRD-PARTY-NOTICES.md。

Full Changelog: v10.8...v11

一加13解容 v10.8

Choose a tag to compare

@aapplle aapplle released this 03 Oct 12:12

v10.8

在 v10.7 基础上,针对 KernelSU 软重启(ksud soft-reboot,内核不重启)的极端场景做了系统性加固,
并修复原值备份被污染导致「执行」回写错误值的问题。

🔴 软重启相关加固

  • skip 标记提前到 ADSP 回写【之前】(重要)
    service.sh 的 60 秒探测循环与后台 adsp_retry 会在等待期间把刚恢复的原值又写回解耦目标。
    实测:回写后 1 秒就被撤销,用户以为恢复失败。现在 skip 先落盘,两个循环立刻停手。

  • post-fs-data.sh:模块已在内存则跳过 rmmod+insmod
    软重启会重跑 post-fs-data,旧逻辑强制重载导致驱动指针 uv_dev 被清空 → 用户必须重新插拔。
    现在检测到已加载就跳过,uv_dev 保留。

  • 清理跨软重启残留的 adsp_retry
    后台进程不会被 soft-reboot 杀掉(实测软重启后同时存在多个)。改用 PID 文件 + 扫描 /proc 兜底;
    匹配模式为 *uv2800/service.sh*,不会误杀其他模块(已用假模块实测验证)。

  • uninstall.sh / action.sh:umount 改用 nsenter -t 1 -m
    越狱模式下 service.sh 用 nsenter 在 PID 1 全局命名空间 bind,普通 umount 够不到那层绑定,
    导致卸载后仍显示真实 SOC。同时注意:/proc/mounts 只显示最顶层,不能用来判断是否绑定。

🟠 备份污染防护(P0)

  • service.sh 新增 backup_orig() 统一入口:读到 <=2900 时拒绝备份
    (C16 的 DT term_coeff 最低档为 3000,真原值必然 >2900;<=2900 只能是本模块写过的值)
  • action.sh 检测 adsp_orig.txt <=2900 则隔离为 adsp_orig.bad,改用 DT 查表兜底
    → 即使旧版本已污染备份,升级后也能自愈

🟢 日志

  • 新增 log.sh:关键日志三路输出(stdout + /dev/kmsg + 备份目录 uv2800.log)
    日志不随卸载清除,便于用户反馈问题;超过 3000 行自动保留后 1500 行
  • uninstall.sh:两个分支都清理 adsp_target,保留 uv2800.log 与 adsp_orig.txt
  • action.sh:okv=skip 分支(回写后缓存已相等时不再提示插拔),结尾提示三态区分

🧪 实测验证(C16 V16.1.0 + KernelSU 3.3.0-53 late-load)

覆盖 7 项软重启极端场景 + 完整回归流程,全部通过:

场景 结果
未插拔直接软重启 ✅ 模块不重载、uv_dev 保持
连续两次软重启 ✅ 全部修复生效
插拔后立即软重启 ✅ 无数据丢失、后台补写成功
disable + 软重启 ✅ 模块留内存、脚本不跑
remove + 软重启 ✅ 目录删除、日志保留
备份目录被删除 ✅ 自动重建
adsp_target 改动 ✅ 按新目标写入

安装包:op13-battery-unlock-v10.8.zip
主文档:PROJECT-vbat_uv-2800.md|历史档案:PROJECT-history.md

一加13解容 v10.6

Choose a tag to compare

@aapplle aapplle released this 03 Oct 07:18

v10.6

修复

  • 解耦写入路径回归主动 adsp_write(重要)
    v10.5 引入的 adsp_force 被动拦截方案经实测证伪:驱动只在「vote 目标 ≠ 当前 ADSP」时才调用 setter 写 term,日常 vote 目标等于当前值,驱动根本不写 → setter hook 全程不触发 → 解耦在标准/越狱两模式都无效(实测 ADSP 停在 3250 不动)。
    本版让内核模块回归原始 v10(无 adsp_force 参数),service.sh 恢复主动 adsp_write 2600。
  • 恢复 adsp_retry 后台补写:越狱模式下插拔充电器后自动补写 ADSP,并补做原值备份 adsp_orig.txt。

修正

  • 越狱(late-load)模式提示措辞:每次重启后插拔一次充电器即可。同一次开机内「安装解耦」与「卸载回写」共用这一次指针捕获,无需重复插拔。

文档

  • 新增 battery_fcc 节点逆向结论:该节点每次读都实时查询 gauge IC,查询失败时(oplus_get_gauge_type 返回 -ENOTSUPP)会回退到 design_capacity,显示的不是电量计真实 fcc。验证解容效果请用驱动 bs_update_data 日志里的 fcc。

实测验证(一加13 / ColorOS 17 / KernelSU 3.3.0)

  • 越狱模式完整安装:ADSP 3250→2600、vbat_uv 2800、bind-mount、禁超级省电、原值备份 3250 ✅
  • 越狱模式完整卸载:ADSP 回 3250、vbat_uv 回 3250、设备策略恢复 ✅
  • 驱动 fcc:ADSP 3250 → 4352;ADSP 2600 → 4996 ✅

安装

用 KernelSU 管理器刷入 op13-battery-unlock-v10.6.zip,重启。

⚠️ 越狱(late-load)模式:每次重启后插拔一次充电器即可(同一次开机内无需重复插拔)。
⚠️ 卸载前请保证电量 >30%,先点「执行」按钮恢复默认再卸载。

一加13解容 v10.5

Choose a tag to compare

@aapplle aapplle released this 03 Oct 04:51

一加13解容 v10.5

越狱(late-load)模式解耦修复:不再依赖设备指针

问题(v10.3 的局限)

越狱模式下模块加载太晚,错过了驱动开机那次 vote,拿不到「驱动设备指针」(uv_dev)。
而 v10.3 的解耦(写 ADSP term = 2600)走的是 service.sh → adsp_write —— 依赖 uv_dev,
所以越狱模式下解耦失效:ADSP 停在 setter hook 强制的 2800,SOC/fcc 仍偏保守。

修复:把 setter hook 的强制值参数化

反汇编依据(COS15/16/17 一致,已逐条核实):

; oplus_gauge_term_voltage_vote_callback
+234  ldr  x0, [x21, #0x30]      ; x0 = *(x21+0x30)
+238  mov  w1, w19              ; w1 = volt
+23c  bl   #0x1c1e44            ; -> oplus_mms_gauge_set_deep_term_volt
                                 ;    内部 blr x8 间接调用 -> oplus_fg_set_deep_term_volt

所以 vote 一触发 → 必然走到模块 hook 的 setter —— 这条路径完全不依赖 uv_dev ✅

内核改动(约 25 行):

项 v10 v10.5
uv_term_set 强制值 固定 UV_TARGET_MV(2800) uv_adsp_force_mv(新参数,默认 2600)
uv_term_get(决定关机电压) 2800 2800(不变)
新参数 — /sys/module/uv2800/parameters/adsp_force

语义分离:uv_term_get 管「何时关机」(2800),uv_term_set 管「电量计模型下限」(默认 2600)。

脚本改动

文件 改动
service.sh 删掉后台重试(v10.3 的死等方案),改为把 adsp_target 同步到内核 adsp_force 参数 —— 不依赖 uv_dev,标准/越狱两模式统一
action.sh 就绪检测仅越狱模式;未就绪提示插拔并等 60s,超时硬中止(exit 1),修掉 v10.3 的「假成功」
customize.sh(新增) 安装时提示(仅越狱模式):需插拔一次充电器 + 无时间限制说明
late-load.sh 同步说明

⚠️ 触发条件(重要)

vote 是事件驱动,不是定时器 —— 只在「充电状态变化」(最可靠 = 插拔一次充电器)或
「深度放电跨档」(极罕见)时触发。所以越狱模式仍需插拔一次充电器:

  • 触发 vote → setter hook 写 2600(解耦,v10.5 起不依赖 uv_dev)
  • 同次 vote → getter 调用 → 捕获 uv_dev(卸载回写用)
  • 一次插拔,两件事同时完成;无时间限制,任意时刻插拔一次即可
  • 不插拔不影响关机保护(getter hook 固定 2800 已生效)

升级方式

KernelSU 管理器直接刷入 op13-battery-unlock-v10.5.zip 覆盖,不需要卸载。

文件

  • op13-battery-unlock-v10.5.zip(md5 bee07961dcb2499dcac302eac83cf711)

一加13解容 v10.2

Choose a tag to compare

@aapplle aapplle released this 03 Oct 02:11

一加13解容 v10.2

修复:解耦配置会静默丢失的问题(adsp_target 无人创建)

问题

adsp_target 是控制「电量计模型下限」的配置文件,但没有任何脚本会创建它:

脚本 行为
service.sh 只读它;缺失时静默回落到默认 2800
action.sh 删它(rm -f)
uninstall.sh 清空整个备份目录

后果:目录一旦被清,解耦(ADSP=2600)就悄悄失效 → 电量计模型下限回到 2800 →
0% 又会提前约 200mV 出现(2983mV 而不是 2865mV),而用户从界面上完全看不出来。

修复

  1. 默认值 2800 → 2600:解耦成为默认行为,不再是"需要手动配置的高级选项"
  2. 首次运行自动生成 adsp_target:缺失时写入默认值,状态可见可改
    (想回到不解耦就手改成 2800;想更保守可写 3000)
  3. 新增 oplusdevicepolicy 异常检测:遇到 Transaction too large
    (/data/system 下策略 XML 堆积导致)时打印明确提示并跳过,
    不再无提示地等满 60 秒超时

验证(本机 ColorOS 17 实测)

ADSP term = 2600 mV
vbat_uv   = 2800 mV
→ ✅ 已解耦(差 200 mV)
adsp_target = 2600(自动生成)
dmesg: uv2800: [adsp_write] 写 2600 mV,返回 0(强制仍生效)

升级方式

KernelSU 管理器直接刷入 op13-battery-unlock-v10.2.zip 覆盖,不需要卸载。

只改用户态脚本(service.sh),内核模块 uv2800.ko 未变。

关于卸载与「原值记录」

uninstall.sh 会清空 /data/adb/uv2800_backup/(含 adsp_orig.txt)。
但这不会导致原值丢失 —— 两条路径都能拿到正确原值:

卸载方式 重装时 ADSP 里的值 结果
先点「执行」回写原值再卸载(推荐流程) 原值(如 3250) 与目标值 2600 不同 → 正确捕获为 adsp_orig.txt ✅
没点「执行」直接卸载 2600(模块写入值) 与目标值相同 → service.sh 的守卫 [ "$cur" != "$ADSP_TARGET" ] 拦住不误捕获 ✅

第二种情况下 adsp_orig.txt 缺失,「执行」按钮会自动改用 DT 表实时计算兜底
(v10.1 起已修复 C15/C16 上查表失败的问题)。

关键:service.sh 只在「当前值 ≠ 目标值」时才把它当作原值备份 ——
恰好挡住了「把模块自己写入的值误当原值」这条错误路径。

文件

  • op13-battery-unlock-v10.2.zip(md5 d657a7672fa1df16110409ccf76a69ed)

一加13解容 v10.1

Choose a tag to compare

@aapplle aapplle released this 03 Oct 01:42

一加13解容 v10.1

修复:「执行」按钮在 ColorOS 15/16 上计算失败(得到 -1)

问题

action.sh 计算原值的逻辑是「取 term_coeff 表里最后一条 count ≤ 当前深度放电次数 的电压」。
但当 deep_dischg_counts 小于表里第一档时,一行都命中不了 → 返回 -1 → 按钮报错。

版本 子节点表 count 档位 count=562 时
ColorOS 17(本机) 1300 ~ 2000 命中 → 3250 ✅
ColorOS 16 800 ~ 1200 无命中 → -1 ✗
ColorOS 15 350 ~ 600 命中(但低 count 时同样会 -1)✗

修复(两处)

  1. 优先使用首次写入前的备份 /data/adb/uv2800_backup/adsp_orig.txt
    —— 那是模块第一次改电量计之前存的真实原值,就是驱动自己算出来的值
    (与本机驱动日志 DEEP_COUNT_VOTER, volt = 3250 完全一致),与 ColorOS 版本无关。
  2. DT 表计算增加兜底:count 低于第一档时取第一档电压(对齐驱动的下界行为),
    不再返回 -1。

验证

场景 修复前 修复后
有 adsp_orig.txt(正常) 3250 3250(来源:备份)✅
备份丢失(C17 表) 3250 3250(来源:DT 表)✅
备份丢失(C16 表,count=562) -1 ✗ 3000 ✅
备份丢失(C15 表,count=100) -1 ✗ 3000 ✅
C15/C16 表,count 正常 正确 不变 ✅

升级方式

直接用 KernelSU 管理器刷入 op13-battery-unlock-v10.1.zip 覆盖即可,不需要卸载。

只改了 action.sh(用户态脚本),内核模块 uv2800.ko 未变。

文件

  • op13-battery-unlock-v10.1.zip(md5 e2cf8003b03c00b21a9bce1d79f72960)

一加13解容 v10

Choose a tag to compare

@aapplle aapplle released this 02 Oct 20:02

一加13解容 v10

把一加 13(PJZ110)的关机截止电压从 3250mV 降到 2800mV,并让电量计原生按新的空电点重算 SOC。

不刷 dtbo、不改任何分区、不碰 system/vendor,卸载无残留。


效果

指标 原厂 解容后
关机电压 3250 mV 2800 mV
满容量 fcc 4346 mAh ~5032 mAh(+15.8%)
0% 对应电压 2983 mV 2865 mV
0% → 关机 217 mV / 9 分钟 83 mV / 0.9 分钟
显示 官方平滑(13W 放电滞后 13 个点) 电量计真实值
低电量 强制进超级省电 已禁用

安装

1) KernelSU 管理器刷入 一加13解容-v10.zip
2) 重启                              ← 唯一需要的一次重启
3) 满电状态下拔插一次充电器(或充满一次)
4) 完成,电量计原生重算 fcc

卸载

1) 【必须】电量充足(>50%)时点模块的「执行」按钮(回写原值 + 恢复策略)
2) 删除模块 → 重启

⚠️ 低电压回写会让电量计进入钳位态(SOC 卡 0%,需充满一次恢复)

兼容性

  • ColorOS 15 / 16 / 17(内核 6.6.30 / 6.6.89 / 6.6.118),自动适配
  • KernelSU 标准模式 + late-load(越狱 / 临时 root)模式
  • 指令字不匹配时拒绝加载,不会乱 hook

风险

  • 2800mV 是硅碳负极放电下限,长期每次放到自动关机会加速电池老化
  • COS15/16 的电量计 term→fcc 行为未经真机验证(最坏退化为「0% 提前」,不损坏硬件)
  • 刷机有风险,请自行备份

文件

  • 一加13解容-v10.zip —— KernelSU 模块(md5 07ae5c2baf3ad245691efce21c7ae550)

完整原理、逆向细节与踩坑清单见仓库文档 PROJECT-vbat_uv-2800.md。

License: GPL-2.0