欢迎食用『主界面』~,这里是赵苦瓜的看板娘desu~

#
小米平板5-Linux使用与排障手册
首页 > 点滴记录    作者:赵苦瓜   2026年11月2日 12:25 星期一   热度:142°   百度已收录  
时间:2026-11-2 12:25   热度:142° 

最后更新:2026-11-04 20:05 机器:Xiaomi Pad 5(nabu)· Ubuntu 26.04 ARM64 · UFS: SAMSUNG KLUFG8RHGB-B0E1 内核:6.14.11-nabu-tmm(快充 + 手写笔充电,自编 idtp9418 模块)/ 6.17.0-xiaomi-nabu-tmm-UFS-slowdown+(自带笔驱动,但只有 5V 慢充) 覆盖:启动与 fastboot 自救 · 充电(含手写笔无线充/蓝牙)· Waydroid 与安卓应用 · 宿主音频 · 亮度音量同步 · 桌面工具链(Markdown 查看 / dsh)


0. 速查总表

场景 症状 处理
启动 卡在 fastboot 出不来 fastboot set_active a → fastboot reboot
启动 7 次启动额度快用完 等桌面通知,补完跑 nabu-boot-count reset
充电 插着电却在掉电 查 v_offset,坏了就拔插一次
充电 只有 5V、输入电流很小 同上,多半是 v_offset 被写坏
充电 低电量插 33W/100W 就抖 先用 18W 充到 40% 再换大功率头
手写笔 吸上去完全没反应 6.14 需要 idtp9418 模块(已装,见 2.4);确认已加载
手写笔 蓝牙搜不到 / 连不上 长按笔按键 5 秒进配对模式,见 2.5
Waydroid 无声音 / 声音断续 见 3.2 两处补丁
Waydroid 起来 40 秒后自己死 完整重建,见 3.4
宿主音频 卡顿时电流声 / 机器人声 量子钉 1024,见 4.1
宿主音频 开杜比后 50% 以上外放炸 9 段拟合 + 零补偿,见 4.2
亮度音量 两端不同步 见 5.1
显示 黑屏/点不亮 / 电源键唤不醒 见附录 F
游戏 碧蓝航线点开闪退(EXIT_SELF 1) 见 3.11,WebView 已修;仍卡 BgcPlugin NPE
游戏 异环黑屏(卡启动闪屏后 SIGSEGV) 见 3.12,libUnreal.so 静态构造空写,容器里基本无解
游戏 任何 app 的 WebView 白屏/闪退 echo '--disable-gpu-compositing' > /data/local/tmp/webview-command-line
文档 双击 .md 只有纯文本、想要渲染 用 dsh 的文档预览,见 6.1–6.2;别装 apostrophe
文档 要打开工作区以外的文件 「文件」面板标题栏的「打开任意文件」按钮,见 6.3
dsh 装插件报 spawn pnpm ENOENT sudo npm i -g pnpm@10,见 6.4

第 1 部分 · 系统与启动

1.1 启动链与分区

ABL(打过 dualboot 补丁)→ boot_a 里的 bootmanager(TWRP-LINUX)
    → ESP(sda31) 上的 rEFInd(菜单 5 秒)→ EFI/ubuntu/*.efi
    → 内核 → root=PARTLABEL=linux (sda32)
  • ESP:/dev/disk/by-partlabel/esp(334MB,sda31),含 EFI/ubuntu/6.14.11.efi、6.17.0-UFS-slowdown+.efi
  • 根分区:linux(sda32,458GB,挂 /)
  • boot_a:TWRP-LINUX(dualboot bootmanager),不要随意刷
  • anti:1(防回滚已开启,别刷旧固件)

1.2 A/B 槽:只有 7 次启动额度

实测事实(2026-11-02):

  • 每次启动 slot-retry-count:a 减 1(实测 5 → 4,一次完整启动)
  • fastboot set_active a 把它重置为 7,同时清掉 slot-unbootable
  • slot-successful:a 永远是 no:Ubuntu 侧没有任何东西上报"启动成功"
  • 减到 0 → ABL 把该槽标记 unbootable → 没有可启动的槽 → 停在 fastboot (11/02 那次事故就是这么来的)
  • 槽元数据不在 AP 可读分区:108 个 <128MB 分区哈希对比,set_active a 前后只有 logfs 变化(UEFI 日志);misc 恒为那 15 个字节、devinfo 全零。 结论:存在 AP 访问不到的保护区(UFS LUN / RPMB / ABL 自有存储),Linux 侧写不了"启动成功"
  • fastboot oem off-mode-charge 1 能写入(连带把 charger-screen-enabled 置 1), 但挡不住插电自动开机,对保住计数没用

建议节奏:

  • 计数到 4 会弹通知,此时还剩 3 次余量,找方便的时间补
  • 平时尽量用挂起代替关机,能显著减少启动次数
  • 别在没接 PC 的情况下重启

1.3 掉进 fastboot 怎么救

# 只读确认
fastboot getvar current-slot        # 期望 a
fastboot getvar slot-retry-count:a
fastboot getvar slot-unbootable:a
fastboot getvar slot-successful:a

# 恢复
fastboot set_active a
fastboot reboot

补完计数:

nabu-boot-count reset     # 计数清零,重新开始算 7 次

1.4 内核怎么选


6.14.11-nabu-tmm 6.17.0-UFS-slowdown+
充电栈 ln8000 电荷泵 qcom-smbx-charger(SMB5)
充电 9V/3A PD PPS,快(−3.8A) 5V DCP,慢(输入约 595mA)
电池电流符号 负 = 充电 正 = 充电(相反)
UFS 时钟 300MHz 201.5MHz(为三星 UFS 降频)
手写笔无线充 ✓ 需自编 idtp9418 模块(已装,见 2.4) ✓ 自带驱动(笔尖朝左吸附)
已知问题 v_offset 可能被写坏 充电慢,status/电流字段不可信
  • 日常推荐 6.14.11(快充可用),充电头用 18W
  • 要给手写笔充电只能切 6.17,代价是 UFS 限速、只能用 5V 慢充(见 2.4)
  • rEFInd 菜单只有 5 秒,注意别选错

1.5 已装工具与数据文件

工具 类型 作用 开关
nabu-charge-guard 用户服务 5 分钟滑窗净放电 >250mA 时弹窗告警(按内核自动处理电流符号) systemctl --user disable --now nabu-charge-guard
nabu-charge-log 用户服务 1Hz 记录充电状态到 CSV systemctl --user disable --now nabu-charge-log
nabu-boot-count 用户 oneshot 启动计数(按 boot_id 去重,注销不会重复计数),到 4 弹窗提醒 systemctl --user disable --now nabu-boot-count
fix-charger.sh 手动脚本 只读诊断(打印 v_offset、电池状态) sudo work/fix-charger.sh

数据位置:

  • ~/.local/share/nabu-charge.csv — 1Hz 充电日志(10MB 自动轮转)
  • ~/.local/share/nabu/boot-count — 启动计数
  • ~/.local/share/nabu/part-hashes-before.txt — 分区哈希基线(考古用,可删)

第 2 部分 · 充电

2.1 v_offset 坏掉是主要故障

指标 正常 坏掉
v_offset(dmesg) 约 150 ~ 420(随输入电压浮动) 10 或 4294xxx(负数溢出)
adc_vin 8200 ~ 8300 5000 ~ 7700
adc_iin 2400 ~ 2700 39(几乎不抽电流)
电池电流 −3.8A(充电) +1.2 ~ +2A(放电)

坏掉后 dmesg 会刷:FAULT_INT / volt_fault_detected (volt_qual=0) / vac_ov_control_work / stop charging ↔ start charging 循环。

原因:v_offset 是 ln8000 驱动内部算的 ADC 偏置。输入电压瞬时跌落时 (大功率 PD 头在 PPS 下调压、电荷泵换工作点),驱动会算出负值,后续所有电压换算全错 → 误判"输入电压不合格" → 反复停充。坏值不会自己恢复,只在驱动初始化时重算。

恢复:拔掉充电器重新插上(驱动重新初始化,v_offset 回到 ~390)。

# 判断是否在坏状态
journalctl -k -n 20 | grep -o 'v_offset=[0-9]*' | tail -1
sudo dmesg | grep adc_vin | tail -2

⚠️ 绝对不要 unbind/rebind ln8000 驱动 实测 echo 1-0051 > /sys/bus/i2c/drivers/ln8000_charger/unbind 触发内核 oops: free_irq → power_supply_unregister → kernfs_remove_by_name_ns 三个 WARNING, 接着 NULL pointer dereference,整机死机,只能长按电源重启。

2.2 充电头与线:低电量才是风险点

实测规律(2026-11-02 当天):

电量 充电头 结果
16 ~ 25% 33W 反复 fault、v_offset 被写坏、净放电
45 ~ 56% 18W 稳定 −3.8A,0 故障
56 → 99% 100W 连续 45 分钟 0 故障、0 掉线,电流从 −5.1A 平滑回落到 −2.1A
  • 低电量时充电电流最大(实测冲到 −5.1A),电荷泵输入电流大、VBUS 更容易跌落, 一跌就是 PD hard reset → v_offset 被写坏 → fault 循环
  • 电量上来后电流自然回落(恒流转恒压),链路就稳了
  • 建议:低于 ~30% 用 18W 或 5V 头;充到 40% 以上再换大功率头,100W 也能长时间稳定
  • 18W 也不是绝对安全(出现过 5V 合约下同样写坏的情况),核心仍是"坏了就拔插"

补充(11/03–11/04 实测):

  • 拔线/插线的瞬间也会出现一次 v_offset 溢出(adc_vin 同时掉到 5~7V), 这是正常瞬变:重新插入时驱动会重新初始化,不影响充电
  • 真正危险的是插着电、充电过程中被写坏:此时会一直卡在 fault 循环,必须拔插一次
  • 26 小时里没有重启,A/B 计数没被消耗
  • 低电量(6~8%)那次的现场:tcpm 电压 0V、电池 +1.5A 放电,重插后立刻恢复 −3.6A
  • 开机前/休眠中线已插着 → 只协商到 5V(11/04 12:36 复现两次): tcpm 只在"重新接入"时做 PD 协商。线如果先插着再开机(或休眠中插电), 它直接以 Type-C 5V 初始化,之后不会自己重试 PD,重启也无效 (因为开机时线还插着,又按 5V 初始化一遍)。 实测:休眠插电 → 5V;v_offset 被写坏、不充;重启后仍是 5V; 真正拔插一次 → 立刻 9V / −4.8A 快充,v_offset 回到 434
  • 判断有没有 PD:cat /sys/class/power_supply/tcpm-source-psy-*/usb_type
    • C [PD] PD_PPS = 有 PD(9V)
    • [C] PD PD_PPS = 只有 Type-C 5V,没 PD ← 需要拔插
  • 5V 下不是完全不能充:PMIC 旁路能给约 0.6A(ln8000 present=0),慢但不断电
  • 软件侧改 input_current_limit 无效,已试过;port_type 切换也推不动 typec

2.3 怎么判断现在稳不稳

# 最近 1 分钟有没有 fault(0 = 稳)
journalctl -k --since '1 minute ago' | grep -c 'FAULT_INT has occurred'

# 20 秒内 Type-C 掉线次数(0~1 = 稳)
timeout 20 udevadm monitor --subsystem-match=typec 2>/dev/null | grep -c port0-partner

# v_offset 是否在正常区间
journalctl -k -n 20 | grep -o 'v_offset=[0-9]*' | tail -1

2.4 手写笔无线充电(IDT P9418,2026-11-04 打通)

一句话:笔能无线充。6.17 自带驱动;6.14.11 原来没有,2026-11-04 已自编模块补上(见本节末尾更新)。

项目 值
充电芯片 IDT P9418(无线充电发射端),i2c-3 / 0x3b,设备树节点 idtp9418@3b
驱动 idtp9418(drivers/power/supply/idtp9418.c,Nicola Guerrera,2025-10-23 进 sm8150-mainline)
电源节点 /sys/class/power_supply/idtp9418(type=Wireless,online / capacity)
自动加载 /etc/modules-load.d/charge-stylus.conf → idtp9418(本次新建)
充电上限 charge_control_end_threshold,默认 85%(echo 100 > ... 可改)
引脚 霍尔 gpio101/gpio134,使能 gpio3,boost gpio47,IRQ gpio95,WPC-DET gpio94(TLMM)
诊断属性 /sys/bus/i2c/devices/3-003b/{chip_version,chip_status,chip_powered}

用法

  1. 用 6.17 内核启动(rEFInd 选 6.17.0-UFS-slowdown+)
  2. 笔笔尖朝左吸到平板顶部短边(方向反了不充)
  3. 看状态:
cat /sys/class/power_supply/idtp9418/{online,capacity}
sudo dmesg | grep idtp | tail -5
# 正常应看到:
#   [idtp] Pen attached! Turning on reverse charging...
#   reverse charging start success
#   [charge-monitor] pen=1 soc=5

桌面「电池实时电压」小组件已加上手写笔一行(~/.local/bin/watch_battery.sh): ● 充电中 12% 上限 85% / ○ 未充电 / 不可用(本内核无驱动)。

实测(11/04 19:52):笔放到完全没电(0%),吸上后 0% → 5% → 10%(约 5%/分钟)。

已知坑

  • SoC 读失败会永久停充:charge-monitor 读到 soc=-1(寄存器 0x3a 返回 0xFF)就打印 stopping reverse charging 并停掉;之后只有把笔取下再吸一次才会重新开始。 重新 modprobe/probe 都不会自动续充(probe 里没有启动充电的路径)。
  • 停充后 online=0、capacity=0、chip_powered=0,但霍尔仍显示"已吸附",别被骗。
  • 电量为 0 的笔刚吸上时最容易触发;先充到 5~10% 再重新吸附基本就稳了。
  • 2026-11-04 20:37 新现象(待验证):蓝牙配对之后,每次吸附都是 reverse charging start success → 连续 soc is default 0xFF → 约 6 秒内停充; 配对前同样的笔从 0% 一路充到 90% 完全正常。怀疑是蓝牙连接状态下笔不响应无线充握手 (或笔发热保护)。验证方法:断开蓝牙再吸附,看是否恢复;如果恢复,就说明是 BLE 干扰。
  • 充电时笔的触控输入会被 NVT 驱动主动关掉(pen_charge_state_notifier),画不了是正常的。
  • 反向充电从平板电池取电(约 5W),长时间吸附会掉电。

为什么只有 6.17 有 / 能不能移植到 6.14.11

  • 时间线:6.14.11 编于 2025-08-14;驱动首个提交在 2025-10-23(sm8150-mainline/linux 863ef95e);6.17 编于 2026-04-11。所以 6.14.11 不是"没开配置",是根本没有这份代码。
  • 设备树绑定也换了:6.14 的 dtb 用小米 BSP 属性名 (idt,irq / hall,int3 / hall,int4 / idt,reverse-enable / idt,reverse-booset-enable), 驱动读的是新绑定(irq-gpios / pen-det-active-high-gpios / pen-det-active-low-gpios / enable-gpios / boost-enable-gpios)。直接拷 .ko 也 probe 不起来。
  • 移植可行,但要动内核:
    1. 需要 6.14.11 的源码树(本机没有,PPA 只发二进制;/lib/modules/6.14.11-nabu-tmm/build 指向作者机器 /home/themojoman/develop/nabu/linux,已失效);
    2. 驱动 794 行,只用 gpiod / regmap / power_supply,6.14 的 API 够用;
    3. 用同 config(/proc/config.gz)编模块,vermagic 要对上 6.14.11-nabu-tmm; 本机 CONFIG_MODVERSIONS=n、CONFIG_MODULE_SIG=n,所以编出来能直接加载;
    4. 最省事:改驱动读旧属性名(不动 dtb/UKI);否则要改 dtb 再用 ukify 重打 UKI;
    5. 真正的风险在电源栈:6.14 是 ln8000 电荷泵 + PD 9V,6.17 是 smb5 5V, p9418 的反向升压共享 VBUS,没在 6.14 上验证过。
  • 取舍:留 6.17 → 能充笔,但只能 5V 慢充、UFS 限速;回 6.14.11 → 快充,但笔不能充。

2026-11-04 21:20 更新:6.14.11 也能充笔了(自编模块)

原来的结论“只有 6.17 有驱动”已过时——给 6.14.11 编了一个 out-of-tree 的 idtp9418 模块, 装上后 6.14.11 也能正常给笔无线充电,平板仍是 9V/3A 快充,两不耽误。

  • 模块位置:/lib/modules/6.14.11-nabu-tmm/extra/idtp9418.ko,开机由 /etc/modules-load.d/charge-stylus.conf 自动加载
  • 源码:~/nabu-build/{idtp9418.c,idtp9418.h},改了两处:
    1. DT 属性名换成 6.14 dtb 里的旧名字(idt,irq / hall,int3 / hall,int4 / idt,reverse-enable / idt,reverse-booset-enable)
    2. GPIO 用 legacy API(of_get_named_gpio + devm_gpio_request_one + gpio_to_desc), 因为 6.14 没有 devm_gpiod_get_from_of_node
  • 编译:linux-6.14.11 源码树 + 本机 /proc/config.gz,vermagic=6.14.11-nabu-tmm 与原内核一致; KBUILD_MODPOST_WARN=1 跳过缺失的 Module.symvers(MODVERSIONS=n,加载时不校验)
  • 实测:笔 0% 吸上后稳定充电(vout 6.75V / iin 128mA ≈ 0.85W / 52~54°C),0%→5%→10%; 蓝牙同时连着也没再出现 0xFF停充
  • 结论:6.14.11 现在“快充 + 笔充电”两全,不用再为笔切 6.17

2.5 手写笔蓝牙(2026-11-04 配对成功)

为什么 MIUI 吸上就自动连,Linux 不会:MIUI 有小米笔系统服务,霍尔检测到吸附就主动唤醒笔并按私有流程配对; Linux/BlueZ 没有这个服务,笔平时不广播(省电),必须长按笔上的按键让它进入配对模式。

配对步骤

  1. bluetoothctl 里 scan on(或设置 → 蓝牙里保持扫描)
  2. 长按笔上的按键约 5 秒
  3. 出现 Xiaomi Smart Pen 后 pair → trust → connect
bluetoothctl --timeout 120 scan on     # 长按笔按键期间扫描
bluetoothctl devices | grep -i pen
bluetoothctl pair C0:2B:3D:70:C6:E9
bluetoothctl trust C0:2B:3D:70:C6:E9
bluetoothctl connect C0:2B:3D:70:C6:E9

笔的蓝牙地址是随机地址(random),重新配对可能变,脚本里别硬编码 MAC,用名字匹配。

能读到的信息(GATT)

服务/特征 内容 实测值
0x180F Battery / 0x2A19 电量 80%
0x180A / 0x2A29 厂商 Xiaomi
0x180A / 0x2A24 型号 wPI07U81PC
0x180A / 0x2A26 固件版本 00.00.0703
0x180A / 0x2A28 软件版本 00.00.0703
0x180A / 0x2A50 PnP ID vendor 0x1915(Nordic)/ product 0xEAEA / ver 0x0001
0x1812 HID Report Map Report 0x10:4 个按键位;0x21:1 字节 feature
0xFE59 Nordic Secure DFU 固件升级服务,别乱刷
  • 没有序列号(0x2A25)和硬件版本(0x2A27);压感/倾角不走蓝牙(走 NVT 触控)
  • GATT 读取要 root(BlueZ D-Bus 策略): sudo busctl --system call org.bluez <path> org.bluez.GattCharacteristic1 ReadValue 'a{sv}' 0
  • 电量普通用户可读:upower -e | grep bluetooth,再 upower -i <path>

两种电量的区别

来源 什么时候有 可信度
WPC(/sys/class/power_supply/idtp9418/capacity) 只在无线充电时;停充后读回 0 粗(笔通过无线协议报,会 80→90 跳变)
BLE(UPower 的 bluetooth_generic_dev_*) 只要蓝牙连着 细(笔自己的电量计)

现在面板/小组件的显示逻辑

  • 充电中:笔XX%(绿,WPC 实时)+ 功率/温度
  • 未充电但蓝牙连着:笔XX%(灰,BLE,顶栏 3 分钟刷新一次)
  • 都没有:不显示(原来的 笔— 容易被看成“笔一”,已去掉)

第 3 部分 · Waydroid 与安卓应用

3.1 最终结论速查

问题 根因 修复
Android 无声音 ro.hardware=unknown,Android 加载了空壳 audio.primary.default.so waydroid.cfg 的 [properties] 里加 ro.hardware.audio.primary = waydroid
Android 无声音(第二层) HAL 启动环境缺 PULSE_RUNTIME_PATH(upstream #2333) overlay 里 init.environ.rc 加全局 export PULSE_RUNTIME_PATH /run/xdg/pulse
声音断续 / 只有宿主也在放时才响几秒 HAL 的 pulse 客户端缓冲不足,ALSA 持续 XRUN(缓冲欠载),设备拿不到连续数据 init.environ.rc 再加 export PULSE_LATENCY_MSEC 60(不够可加到 120)
容器起来 ~40 秒后自己死 未重建 LXC 配置就 systemctl restart 必须先 rm -rf /var/lib/waydroid/lxc && waydroid init 再启动
Session start 报 DBus NoReply 用 sudo 启动了 session session 必须以普通用户身份启动
Android 启动到「手机正在启动」后无限重启 /data 属主被 chown -R 改坏,keystore2 打不开数据库 只能重置 /data(见「事故复盘」)

3.2 当前生效的配置(持久化)

1. /var/lib/waydroid/waydroid.cfg

[properties]
ro.hardware.audio.primary = waydroid

作用:让 Android 加载 /vendor/lib/hw/audio.primary.waydroid.so,而不是 default 空壳。

2. /var/lib/waydroid/overlay_rw/system/init.environ.rc

内容是在原文件基础上,在 on early-init 块里插入两行:

on early-init
    export PULSE_RUNTIME_PATH /run/xdg/pulse
    export PULSE_LATENCY_MSEC 60
    export ANDROID_BOOTLOGO 1
    ...

作用:

  • PULSE_RUNTIME_PATH —— 让所有 Android 服务(含 audio HAL)继承 pulse socket 路径
  • PULSE_LATENCY_MSEC —— 给 libpulse 客户端设缓冲延迟。不设这一行,HAL 会持续欠载(XRUN),表现为声音断续或干脆没声,且症状很迷惑:宿主自己也播放时,设备被"带"起来,Android 的声音会顺带响几秒

⚠️ 注意:这两个补丁放在 overlay 里。如果执行 waydroid init -f 且走到 preinstalled images 分支,overlay 会被清空,补丁需要重新加。


3.3 验证音频是否正常(几条命令)

# 1. HAL 是否加载正确
adb shell getprop ro.hardware.audio.primary     # 应输出 waydroid
PID=$(adb shell pidof android.hardware.audio.service | tr -d '\r')
sudo waydroid shell -- sh -c "grep -oE '[^ ]*audio\.primary[^ ]*\.so' /proc/$PID/maps"
# 应输出 /vendor/lib/hw/audio.primary.waydroid.so

# 2. HAL 环境里有没有 pulse 路径和延迟参数
sudo waydroid shell -- sh -c "tr '\0' '\n' < /proc/$PID/environ | grep PULSE"
# 应输出:
#   PULSE_RUNTIME_PATH=/run/xdg/pulse
#   PULSE_LATENCY_MSEC=60

# 3. 播放时看宿主 PipeWire 有没有 Waydroid 的音源
wpctl status | sed -n '/Streams:/,/Video/p'
# 应看到 "Waydroid" + output_FL/FR -> Speaker playback [active]

# 4. 想确认送出去的不是静音,可以录 sink 监听
pw-record --target=61 /tmp/mon.wav      # 61 是 sink 节点 id,用 wpctl status 查
# 静音时峰值 0;有声时峰值明显大于 0

# 5. 【关键】播放时检查 ALSA 是否在欠载
cat /proc/asound/card1/pcm0p/sub0/status | head -1
# 正常:持续 state: RUNNING
# 异常:SETUP / XRUN 来回跳 —— 这就是断续无声的元凶,加 PULSE_LATENCY_MSEC 解决

3.4 正确的启停方式(重要)

启动(推荐)

# 普通用户身份,不要 sudo
export XDG_RUNTIME_DIR=/run/user/1000
export WAYLAND_DISPLAY=wayland-0
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus

sudo systemctl start waydroid-container
waydroid session start &
waydroid show-full-ui

或者直接点桌面「Waydroid 安卓」图标(启动脚本会处理这些)。

停止

waydroid session stop        # 会连容器一起停

⚠️ 如果容器「起来了但几十秒后自己死」

不要用 systemctl restart,走完整重建:

waydroid session stop
sudo systemctl stop waydroid-container
sudo rm -rf /var/lib/waydroid/lxc
sudo waydroid init          # 重新生成 LXC 配置,不重新下镜像
sudo systemctl start waydroid-container
waydroid session start &    # 普通用户

这条是今晚反复踩坑总结出来的:单纯 restart 必挂,重建 lxc 目录后能稳定启动。


3.5 事故复盘(别重蹈覆辙)

事故 1:chown -R 毁掉 /data(最严重)

为了「修复权限」,对 ~/.local/share/waydroid/data 执行了 chown -R neko:neko。

后果:

  • Android 的 system 用户恰好也是 uid 1000,所以 /data/system 等文件没事
  • 但 keystore 是 uid 1017,/sdcard 是 1023,各 app 是 u10xxx —— 全被改成 1000
  • keystore2 打不开 persistent.sqlite → abort → system_server 崩 → init 重启 → 无限循环
  • 现象就是「能看到手机正在启动,几秒后重启」

教训:永远不要对整个 /data 做递归 chown。 需要修单个目录时,先确认它应有的 uid:

目录 应有 uid
/data/system、/data/misc/*(多数) 1000(system)
/data/misc/keystore 1017
/data/media、/sdcard 1023(media_rw)
/data/misc/adb 0(root)

事故 2:改 init.waydroid.rc 导致容器起不来

尝试在 /system/etc/init/init.waydroid.rc 的 service vendor.audio-hal 块里加 setenv PULSE_RUNTIME_PATH ...,结果容器直接起不来(LXC 运行 40 秒后退出)。

正确做法是改 init.environ.rc 的全局 export(本记录 3.2)。

事故 3:lxc.environment 不生效

在 config_base 里加 lxc.environment = PULSE_RUNTIME_PATH=... 后容器能启动,但 Android init 不会把 LXC 的环境传给它的服务,HAL 的 /proc/<pid>/environ 里依然没有。

事故 4:用 sudo 启动 session

sudo waydroid session start 会让 DBus 调用超时(NoReply),表现为「管理器根本没反应」,极容易误判成 Waydroid 坏了。


3.6 网络相关

机器访问 SourceForge / GitHub Release 极慢(实测 SourceForge 单流 325 B/s,8 线程合计 3 KB/s)。

用 Clash 代理(FlClash GUI,混合端口 7890)后正常:

# 首次初始化镜像
sudo env https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 \
     no_proxy=ota.waydro.id waydroid init -f

注意:

  • no_proxy=ota.waydro.id 让元数据走直连(有时节点反而连不上 GitHub Pages)
  • 走代理实测 6–8 MB/s,912 MB 系统镜像几分钟能下完
  • 如果没有代理,可以用 /etc/waydroid-extra/images 预置 system.img + vendor.img 跳过下载

3.7 adb / 文件传输

sudo waydroid adb                           # 开启容器内 adb
adb connect 192.168.240.112:5555
adb devices                                 # 应显示 device

如果显示 unauthorized,把宿主公钥灌进去:

D=~/.local/share/waydroid/data
sudo mkdir -p $D/misc/adb
sudo cp ~/.android/adbkey.pub $D/misc/adb/adb_keys
sudo waydroid shell -- setprop ctl.restart adbd
adb disconnect 192.168.240.112:5555; adb connect 192.168.240.112:5555

日常传文件用桌面「安卓文件传输」工具(GTK4 写的,支持拖拽、双向、目录浏览),或:

adb push 文件 /sdcard/Download/
adb pull /sdcard/DCIM/xxx.jpg ~/图片/

3.8 关键路径清单

路径 说明
/var/lib/waydroid/images/ system.img(1.9G)+ vendor.img(403M),只读挂载
/var/lib/waydroid/overlay_rw/ 系统分区写入层(我们的 init.environ.rc 补丁在这)
/var/lib/waydroid/waydroid.cfg 主配置(含 [properties] 自定义属性)
/var/lib/waydroid/waydroid.prop 每次启动生成的属性文件
/var/lib/waydroid/lxc/waydroid/ LXC 配置(config、config_session、config_nodes)
~/.local/share/waydroid/data/ Android /data(用户数据、已装 app)
/usr/lib/waydroid/data/configs/config_base LXC 配置模板
/usr/lib/waydroid/data/scripts/waydroid-net.sh 网桥脚本(start/stop)

3.9 常见现象对照表

现象 大概率原因 处理
Session: RUNNING, Container: STOPPED 容器启动失败 走「完整重建」流程
起来 40 秒后自己死 未重建 lxc 配置 同上
NoReply / 管理器没反应 用 sudo 起 session,或管理器卡死 用普通用户;必要时 systemctl restart
开机画面后无限重启 /data 属主损坏(keystore) 只能重置 /data
无声音 HAL 加载错 / 缺 PULSE 环境 见 3.2 两处补丁
声音断续,或只有别的程序放声音时才响几秒 ALSA XRUN 欠载 init.environ.rc 加 PULSE_LATENCY_MSEC 60,不够改 120
声音突然变成"机器人/炸麦",过一阵自己恢复 音频图量子被压到 512,与 Waydroid 720 帧周期不对齐 force-quantum/min-quantum 钉 1024,见 4.1
界面卡顿时有电流声/爆音(跟 Android 无关) PipeWire 无实时优先级 + 缓冲偏小 见 4.1
开杜比音效后,音量 50% 以上外放"炸" 20 段 EQ 级联比曲线深 5 dB,补偿增益把功放推进保护 见 4.2:9 段拟合 + 零补偿 + −1 dBFS 保险
开杜比后同音量明显变轻 校正本身是减法,净衰减约 7 dB 正常,靠音量旋钮补,见 4.2
adb unauthorized 容器 /data 里没有宿主公钥 见 3.7
首次 init 卡在下载 SourceForge 被限速 挂代理,或预置镜像
桌面图标点了没反应 容器没起 / session 没起 先手动 waydroid session start 看报错

3.10 一次性恢复脚本(保留数据)

#!/bin/bash
# 保留 /data 和镜像,只重建运行环境
export XDG_RUNTIME_DIR=/run/user/1000
export WAYLAND_DISPLAY=wayland-0
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus

waydroid session stop 2>/dev/null
sudo systemctl stop waydroid-container
sleep 2
sudo pkill -f /usr/bin/waydroid
for m in $(mount | grep waydroid | awk '{print $3}' | sort -r); do sudo umount -l "$m"; done
sudo rm -rf /run/waydroid-lxc /var/lib/waydroid/lxc

sudo waydroid init
sudo systemctl start waydroid-container
sleep 6
waydroid session start &
sleep 40
waydroid status

3.11 游戏闪退排查:碧蓝航线(2026-11-03,进行中)

现象

点「碧蓝航线」图标 → 图标闪一下 → 黑屏 → 回桌面。启动后 1~5 秒必退, Unity 会话计数一度累积到 11 次,全部失败。

结论(一句话)

不缺资源、不缺内存、不是网络、也不是安装包损坏。 是 app 自己的加密脚本包没被解密/加载出来,Unity 只能拿 APK 里那套"壳"元数据跑空场景, 脚本全部解析不到,于是进程主动 exit(1) 自杀。

环节 证据
系统侧判定 dumpsys activity exit-info 全部 reason=1 (EXIT_SELF) status=1,无 ANR、无 LMK
崩点 Unity 载 level0 时 referenced script (...BLHX.Rendering...) is missing! + different serialization layout (Read 32 bytes but expected 56),随后 exited cleanly (1)
代码不在 il2cpp global-metadata.dat 里 BLHX 只有 266 处,且都是 BLHX.HotUpdate / BLHX.Rendering.HotUpdate 这类基础设施;ScriptingAssemblies.json 里挂着 Yongshi.BLHotUpdate.Runtime.dll
代码在加密包里 assets/AssetBundles/scripts64.ys(38 MB)头部是 52 AA 2A A5,不是标准 UnityFS;同目录 hotupdate.ys / custom_builtin.ys 才是正常 UnityFS
内容不缺 assets/AssetBundles/ 共 37536 个文件、未压缩 1644 MB,全在 APK 里;Download/ 为空是"没走到下载",不是"缺下载"
APK 完整 libil2cpp.so / libcrypto.1.1.so / global-metadata.dat 的 CRC 与 APK 内原件逐个比对完全一致;splits=[base]
网络 抓了启动全过程容器对外连接:只有 adb,没有任何 CDN 请求——它压根没尝试下载
内存 11.6 GB 总量 / 6.3 GB 可用

⚠️ 一个必须记住的坑:ro.build.type 别改 user

属性伪装第一版顺手把 ro.build.type 也改成 user,重启后开机弹 「Android 系统:您的设备内部出现了问题。请与您的设备制造商了解详情」:

E/Build: Vendor interface is incompatible, error=1
E/ActivityTaskManager: Build fingerprint is not consistent, warning user

原因:AOSP 的 Build.isBuildConsistent() 在 IS_ENG || IS_USERDEBUG 时直接返回 true(短路)。 改成 user 后不再短路,而这张镜像的 VINTF HAL 矩阵本来就不匹配, VintfObject.verifyWithoutAvb() 返回 1 → 必然判不一致 → 弹窗。

结论:ro.build.type 必须保持 userdebug。 已撤回,弹窗消失。其余伪装可留。

当前生效的伪装(已验证系统无异常)

文件 作用
/var/lib/waydroid/waydroid.cfg → [properties] 持久化(下次 waydroid init 也会带上)
/var/lib/waydroid/waydroid_base.prop 立即生效(session start 只读这个,不回读 cfg)

两边必须同步写——只改 cfg 不生效;跑 waydroid init 又会重写 LXC 配置(3.4 那条"必挂"路径)。

[properties]
ro.build.fingerprint = Xiaomi/nabu/nabu:13/TKQ1.220829.002/V14.0.5.0.TKXCNXM:user/release-keys
ro.build.tags = release-keys
ro.product.waydroid.brand = Xiaomi
ro.product.waydroid.manufacturer = Xiaomi
ro.product.waydroid.device = nabu
ro.product.waydroid.name = nabu
ro.serialno = 8H2X9K3LM

实测变化:brand waydroid→Xiaomi、manufacturer Waydroid→Xiaomi、 device/name →nabu、fingerprint 112 字符→71 字符、tags test-keys→release-keys。

顺带修掉一个真问题:原来 ro.build.fingerprint 有 112 字符 > PROP_VALUE_MAX(92), native 层 __system_property_get("ro.build.fingerprint") 一律拿到空串 (日志里 E libc: The property ... is too large for __system_property_get())。 现在是 71 字符,native 也能正常读到。

备份:/var/lib/waydroid/waydroid.{cfg,base.prop,prop}.bak-20261103-192221

回滚:

sudo cp -a /var/lib/waydroid/waydroid.cfg.bak-20261103-192221 /var/lib/waydroid/waydroid.cfg
sudo cp -a /var/lib/waydroid/waydroid_base.prop.bak-20261103-192221 /var/lib/waydroid/waydroid_base.prop
waydroid session stop; sudo systemctl start waydroid-container; waydroid session start &

改完属性后:症状变了,但没修好

  • BgcPluginManager ... onlyDownloadAndInstall() 的 NullPointerException 不再出现
  • 游戏仍在同一处 exit(1),Download/ 依旧为空
  • 对照(19:40 更正,重要):同机 com.bilibili.priconne(公主连结)能正常进主界面(实测 pid 在跑, topResumedActivity=com.bilibili.priconne/.MainActivity),而且它挂的是同一套反外挂 (libthemis.so / libcustom_rules.so / libbili.so / libmsaoaid*.so), B站 SDK 采到的设备档案同样是 is_emu=001000、is_debug=1。 它 exit-info 里的 EXIT_SELF 全部来自 :GP7Service / :GP7Worker 子进程且 status=0(正常退出), 和碧蓝航线"主进程 status=1 自杀"完全不是一回事。 → 模拟器识别、反外挂拦截、环境本身,都不是本次闪退的原因。

→ 属性伪装既不是必要条件也不是充分条件:公主连结在同样的 is_emu=001000 + 同一套反外挂下能正常跑, 说明这些特征本身不致命。改属性没修好游戏,现在保留它只是为了顺手修掉 "fingerprint 112 字符导致 native 读不到"这个真实缺陷,随时可按上面的命令回滚。

要继续往下查可选的路

  1. 换 Android 11(LineageOS 18.1)镜像复测 —— 重装级操作,流程见 3.4 / 3.10;
  2. 用 debuggable 重打包 APK 附加调试 —— 会毁签名、有封号风险,不建议;
  3. 放弃在 waydroid 里跑这个游戏,用真机或官方 PC 端。

19:44 两处更正 + 最新进展

更正 1:公主连结的下载不在 /sdcard。 它把资源下到了内部目录,共 11 GB:

/data/data/com.bilibili.priconne/files/   a/ 3.6G  v/ 2.9G  m/ 1.6G  s/ 1.5G  b/ 1.4G  manifest 55M

之前只看 /sdcard/Android/data/.../files/Download(4 KB)就断定"它没下载也能跑",结论作废。 查这类游戏的资源体积要直接 du -sh /data/data/<pkg>,别只看外部目录。

更正 2:碧蓝航线并不是"点开必闪退"。 做完整清理(pm clear + sudo rm -rf /sdcard/.../Android/data/com.bilibili.azurlane)后, 它能正常弹出用户协议页并存活数十秒,pss=315MB / rss=501MB(Unity 确实起来了):

19:42:42  am_proc_start 29769  (首次启动,弹出协议页)
19:43:22  29769 EXIT_SELF status=1  (未点"同意",exit)
19:43:30  am_proc_start 30990  (被重新拉起,再次弹协议页)
19:43:34  30990 EXIT_SELF status=1
19:43:37  31315 (仅 XprefProvider content provider)

→ 之前"秒退"里有一部分是外部残留目录(/sdcard/.../files/il2cpp 还在)让启动器误判状态造成的。 清理干净后偶尔能走到协议页,但不稳定。

19:47 实测:协议框抢不到。 清数据后连续 3 次尝试"启动 + 连点同意"(坐标 1314,990, 从截图量出来的按钮中心),三次都没点中:进程在 4~10 秒内就 exit(1),截图取回来时已回到桌面。 协议框只在 19:42 那一次实例里稳定出现过(活了约 40 秒),之后的启动因为协议已记录, 直接进 Unity → 脚本缺失 → 4 秒死。

稳定的失败循环(可复现):

启动 → Unity 载 level0 → BLHX.* 脚本全 missing + 序列化布局不匹配(32/56) → exit(1) → 被再次拉起 → ...

结论:卡点不在"同意"这一步。 签协议只是把流程往后推了一格,真正的死点在 Unity 载入壳场景, 要过这一关只能让解密脚本包 scripts64.ys 成功加载——目前没有黑盒手段能做到。

19:52 用宿主 gdb / strace 取证(不用改 APK)

关键结论:exit(1) 是 Java 层从网络回调线程里调用的。

容器里的进程在宿主上可见(ps -eo pid,args | awk '$2=="com.bilibili.azurlane"'), 有 sudo 就能直接 ptrace,完全不需要 debuggable 重打包:

# 1) 找到宿主侧 PID
PID=$(ps -eo pid,args | awk '$2=="com.bilibili.azurlane"{print $1; exit}')

# 2) 在 exit_group 上断点,抓退出时的调用栈
sudo gdb -p $PID -batch -ex "catch syscall exit_group" -ex "continue" -ex "info threads" -ex "bt 25"

# 3) 看它打开了哪些文件(找缺失的资源)
sudo strace -f -p $PID -e trace=openat,openat2 -o /tmp/az.trace

抓到的东西:

观察 说明
exit_group 的调用线程是 iligame.com/...,栈在 libart.so 的 nterp_helper 里 是 Java 网络回调调用的 System.exit(1),不是本地 native 崩溃
openat(".../files/il2cpp/il2cpp.usym") = ENOENT、/system/bin/il2cpp.usym = ENOENT 游戏在找 il2cpp.usym,APK 里没有这个文件(只有 3 个 libil2cpp.so),只能由热更下发
openat(".../files/il2cpp/Metadata/global-metadata.dat") 成功 元数据确实是从外部目录加载的
newfstatat(".../base.apk/assets/bin/Data/level0") = ENOTDIR Unity 直接按 APK 内路径找壳场景(level0 只有 58 KB)

抓包(tcpdump -i waydroid0)显示启动期间访问的全是 443/TLS, SNI 里有 p.biligame.com(补丁服务器)、api.biligame.net、line1-sdk-app-api.biligame.net、 game.bilibili.com、s1.hdslb.com。没有任何资源下载流量,但补丁接口是访问过的。

→ 合并起来看:大概率是补丁/SDK 接口的某个响应让客户端决定退出(版本、风控、实名或维护), 而不是本地解密失败。要看到响应内容必须 MITM(装 CA,且很可能撞证书固定)。

20:10 突破:协议页 WebView 渲染线程崩溃,用命令行开关修好了

之前"协议框只闪一下就消失"的真相不是 UI 抢不到,是 WebView 渲染线程把自己崩了:

pid 2696, tid 2831, name: RenderThread >>> com.bilibili.azurlane
signal 0 (SIGSEGV), code 1 (SEGV_MAPERR), Cause: null pointer dereference
#00 libwebviewchromium.so   (webview.apk!libwebviewchromium.so)

对应更早的 E libEGL: eglMakeCurrentImpl error 3008 (EGL_BAD_DISPLAY) 和 E/OpenGLRenderer: Device claims wide gamut support, cannot find matching config。 WebView 在走 GPU 合成时崩在 Mesa/gbm 上。

修复:给 WebView 一个命令行参数文件(全局生效,所有用到 WebView 的 app 都受益)

adb shell "echo '--disable-gpu-compositing' > /data/local/tmp/webview-command-line"
  • 只写 --disable-gpu-compositing:协议页正常渲染、进程不崩(实测截图正常)
  • 写成 --disable-gpu --disable-gpu-compositing:不崩,但页面渲染成白块——不要用这个
  • 回滚:adb shell rm /data/local/tmp/webview-command-line

修好之后,协议流程能完整跑通:

agreement.start.api → agreement.page.view → agreement.page_load.api
→ agreement.agree.click → agreement.finish.api
→ patch_req_config → patch_config_net_success → bgc_plugin

但随后仍卡在同一个老毛病(见下节): BgcPluginManager.onlyDownloadAndInstall() on a null object reference → exit(1)。 所以 WebView 只是第一道坎,资源准备那道坎还在。

20:10 期间的两次容器宕机(教训)

为了装 MITM 证书改了 overlay_rw/system/etc/security/cacerts/,需要重启容器才生效。结果:

  • 普通 systemctl restart 开始随机失败,症状是 zygote 崩: JNI FatalError: set_sched_policy(0, -1) failed: Operation not permitted (cgroup 只读,和 3.9 表里那条一致)
  • 容器崩过一次之后,残留挂载会让后续启动全部失败,必须先清干净: systemctl stop → umount 所有 waydroid 挂载 → rm -rf /run/waydroid-lxc /var/lib/waydroid/lxc → waydroid init → start
  • waydroid init 会 rmtree 掉 overlay_rw(源码 images.py: remove_overlay), 所以放在 overlay 里的证书每次重建都会丢——这就是 MITM 方案在这里走不通的根本原因
  • 另外 pkill -f /usr/bin/waydroid 会匹配到自己的命令行,把自己杀掉,别这么写

结论:这个环境不适合做需要重启容器的实验。做之前先想清楚怎么恢复。

20:12 试过但无效:把缓存配置里的 bgc_plugin 关掉

思路:files/cdn_s.0 里 sdk_switch.bgc_plugin = 1,怀疑关掉能绕过那个 null manager。

文件结构(已确认):Java 序列化头(27 字节)+ 一段 6753 字节的 JSON 串,无尾部校验和, 所以可以等长替换 "bgc_plugin": 1 → "bgc_plugin": 0,不破坏格式:

# 备份
sudo cp -a .../files/cdn_s.0 /tmp/
# 等长替换(用 python 校验 JSON 仍可解析、长度不变)
# 再 chattr +i 防止被启动时的刷新覆盖
sudo chattr +i .../files/cdn_s.0

结果:NPE 照样出现,onlyDownloadAndInstall() 依旧打在 null 上。 → sdk_switch.bgc_plugin 不是那个 manager 为 null 的原因。 已回滚(chattr -i + 还原备份,确认 bgc_plugin=1、大小 6780 未变)。

附:这次用到/可复用的取证命令

adb connect 192.168.240.112:5555
adb logcat -b all -d -v threadtime | grep -E "CRASH|BgcPlugin|Unity|has died"
adb shell dumpsys activity exit-info com.bilibili.azurlane
# adb root 在 waydroid 不可用(会回 “not available in Waydroid”),直接走宿主文件系统
sudo ls ~/.local/share/waydroid/data/data/com.bilibili.azurlane/
sudo ls ~/.local/share/waydroid/data/data/com.bilibili.azurlane/shared_prefs/
sudo cat ~/.local/share/waydroid/data/data/com.bilibili.azurlane/shared_prefs/device_data.xml
# 看 il2cpp 元数据里到底有没有游戏类
sudo strings -a ~/.local/share/waydroid/data/media/0/Android/data/com.bilibili.azurlane/files/il2cpp/Metadata/global-metadata.dat | grep -c BLHX

完整时间线与字段级证据另存: ~/文档/deepseek-harness/default-workspace/azurlane-crash-分析报告.md


3.12 异环(UE5)黑屏:库初始化阶段就崩了(2026-11-03)

现场

  • 包名 com.hottagames.yh.laohu(注意:com.hypergryph.endfield 是明日方舟终末地,别搞混),版本 1.4.0
  • 引擎:Unreal Engine 5(com.epicgames.unreal.GameActivity),libUnreal.so 有 241 MB
  • 症状:点开是「黑屏」——其实是停在 Android 12 启动闪屏(黑底 + 图标), 进程 10~20 秒后 SIGSEGV,exit-info 里是一长串 reason=2 (SIGNALED) status=11

取证(宿主 gdb 抓崩溃现场)

PID=$(ps -eo pid,args | awk '$2=="com.hottagames.yh.laohu"{print $1; exit}')
sudo gdb -p $PID -batch -ex "continue" -ex "bt 18"
Thread 1 "agames.yh.laohu" received signal SIGSEGV
#0..#14  libUnreal.so
#15  __dl__ZN6soinfo17call_constructorsEv ()        <- 静态构造函数
#16  __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv ()
#17  __dl___loader_dlopen ()

崩的那条指令(pc - 库基址 = 0x289D97C,用 info proc mappings 算的):

ldr  x10, [x10, #2408]     ; 取全局表
ldr  x0,  [x10]            ; x0 = 0   <- 表项还没建
strb w10, [x9, #1192]      ; 置"已初始化"标志
str  x8,  [x0, #16]        ; 空指针写 -> SIGSEGV

结论:不是"渲染不出画面",而是 libUnreal.so 被 dlopen 时, 静态构造函数往一个还没初始化的全局注册表里挂对象,直接空指针写死了——引擎 main 根本没进。

排除项

怀疑 结论
安装损坏 否:libUnreal.so 与 APK 内原件 CRC 完全一致(251901120 / 370f2d23)
渲染/图形栈(Mesa、Vulkan) 否:崩在 dlopen 构造函数里,与 EGL/Vulkan 无关(Vulkan ICD 也都在:freedreno / lvp / virtio)
属性伪装 / WebView 修复的副作用 否:这游戏 18:20 装的、19:03 就被清过数据,都早于我们的改动
缺资源 否(是结果不是原因):内部数据 96 K、外部 16 K;files/ano_tmp/ 里一堆 0 字节 ms_*.dat(腾讯 ANO 反外挂的内存扫描残留)

判断

库级初始化失败,在 waydroid 这种容器里基本无解:要么是 UE5 对这套 Android 13 + Mesa 的兼容问题, 要么是 ANO 反外挂注入让静态初始化炸了。 换 Android 11 镜像不一定能好(不是渲染问题,是加载问题),不建议为它重装。

想要跑异环只能真机 / PC 端。

官方配置要求(2026-04-23 公测,官网 FAQ)

项 最低 推荐
Android 10.0+ —
SoC 骁龙 855 / 天玑 8000 / 麒麟 990 骁龙 8 Gen2 / 8 Gen3 / 天玑 9200 / 麒麟 9000S
存储 预留 20 GB —
iOS iPhone 12 Pro Max、iOS 15 iPhone 14 Pro、iOS 17

我方设备:骁龙 860(≈855+)、Android 13、存储 483 GB → 名义达标,但只到"最低"线, 即使能启动,UE5 开放世界在这颗 855 级 SoC 上也基本跑不动。

官方对"移动端下载完打开就闪退"的建议是卸载重装并保持前台完整更新; 但本例崩在 libUnreal.so 静态构造(见上),APK 完整、无分包(1.4 GB 单包、splits=[base])、 页大小 4 KB 正常,所以重装大概率无效。

参考:雷电模拟器能跑(官方设置文档, 要求 14.0.5.1+、6 GB 内存 4 核),但那跑的是 x86 Windows 模拟器,与 waydroid 的 arm64 路径不同。

20:27 按官方建议卸载重装 —— 实测无效

sudo cp <app>/base.apk ~/yh_base.apk      # 先备份 1.4 GB 安装包
adb uninstall com.hottagames.yh.laohu
adb install -g -r ~/yh_base.apk           # 17 秒装回
adb shell am start -n com.hottagames.yh.laohu/com.epicgames.unreal.GameActivity

结果:一模一样——停在 Android 启动闪屏(黑底+图标)→ 十几秒后 reason=2 (SIGNALED) status=11(SIGSEGV)→ 再次拉起 → 再崩。

坐实了:不是安装/资源/配置问题,就是 libUnreal.so 在这套容器里静态初始化失败。 这条路到此为止。


第 4 部分 · 宿主音频

4.1 系统级音频杂音:卡顿时有电流声(与 Waydroid 无关)

现象

  • 界面卡顿(切大页面、加载重内容)时,声音里出现明显电流声 / 噼啪声
  • 退出卡顿页面、或暂停后重新播放,往往就恢复
  • 跟 Android 无关,宿主播放音乐视频同样会出现

排查(先排除「四扬声器不同步」)

Xiaomi Pad 5 的 4 颗 CS35L41 功放由同一条 TDM 链路、2 声道驱动:

# /usr/share/alsa/ucm2/Xiaomi/nabu/HiFi.conf
#  - Internal Speakers : CS35L41 driver : MultiMedia1 <-> QUAT_TDM_RX_0
SectionDevice."Speaker" {
    PlaybackChannels 2
}

用户态只有一条 2 声道流,驱动/硬件层已经同步,不可能由软件造成四扬声器不同步。真不同步会表现为声像偏移、低频抵消发闷,而不是随负载出现的爆音。

真正原因(两层,第二层才是根因)

第一层:没有实时优先级

journalctl --user -u pipewire | grep -c 'out of buffers'     # 曾达 164 次
ps -o rtprio,comm -p $(pgrep -x pipewire)                    # RTPRIO  -  ← 没有 RT

第二层(根因):音频图量子被压到 512

default.clock.quantum 只是偏好值,压不住客户端请求的低延迟。某个客户端(浏览器等)要 512 帧,整张图就掉到 512:

R   48    512  48000  ...  ERR 3225    ← 扬声器 sink,错误持续累积
R  108    720  48000  ...  ERR 163     ← Waydroid 流,周期 720 帧
              ↑ 512 与 720 不成整数比 → 实时重采样 + 高错误率

表现就是声音时好时坏、突然像"炸麦/机器人说话",取决于当时连进来的是哪个客户端。这一点比单纯欠载更迷惑人:它不随负载变化,而是随"谁在放声音"变化。

修复

(1)实时优先级(辅助)—— /etc/security/limits.d/25-pw-rlimits.conf 已给 @pipewire 组 rtprio 95:

sudo usermod -aG pipewire neko     # 需要重新登录/重启才生效

注意:实测这台机器上即使 ulimit -r 是 95,chrt -f 80 仍返回 EPERM,PipeWire 拿不到 SCHED_FIFO。所以不要指望这一项,主战场是下面的量子。

(2)把量子钉死(关键) —— ~/.config/pipewire/pipewire.conf.d/10-stability.conf:

context.properties = {
    default.clock.quantum       = 1024
    default.clock.min-quantum   = 1024    # 关键:设成 512 等于没设
    default.clock.max-quantum   = 4096
    default.clock.force-quantum = 1024    # 关键:强制,忽略客户端请求
}

min-quantum 和 force-quantum 两行缺一不可——只改 quantum 就是这次的弯路,客户端照样能把图压到 512。

临时生效(不重启):

pw-metadata -n settings 0 clock.force-quantum 1024

验证

# 看量子是否被钉住
pw-metadata -n settings | grep -E 'force-quantum|min-quantum'

# 看错误计数是否停止增长(隔 10 秒读两次对比)
timeout 8 pw-top -b -n 3 | grep -E 'alsa_output|Waydroid'
# ERR 列不再增长 = 稳定

实测:修前 sink ERR 一路涨到 3225;固定 1024 后 10 秒、30 秒两次采样均零增长。

注意

  • 不要单独重启 PipeWire:Waydroid 的 pulse socket 是绑定挂载进容器的,PipeWire 一重启容器里那个 socket 就变陈旧的 inode,Android 立刻没声音。要么重启整机,要么重启 PipeWire 后走3.4「完整重建」把容器重拉一遍。
  • quantum 1024 ≈ 21 ms 延迟。看视频/听歌无感;打音游觉得延迟大可以降到 512,但那就回到"容易被客户端拖下去"的老问题。
  • 这台机器的延迟预算:PULSE_LATENCY_MSEC 60(Waydroid 侧)+ quantum 1024(系统侧)是两处独立缓冲,调一处不影响另一处。

4.2 外放调音:把 MIUI 的杜比参数搬到 Linux

结论

杜比的算法搬不过来(Android AudioEffect + 厂商 DSP 标定,且授权绑定),但调音参数是明文:/vendor/etc/dolby/dax-default.xml(DAX3 Android,format 3.x)。单位是 1/16 dB,-192 就是 −12 dB。

XML 节点 含义
audio-optimizer-bands 逐机喇叭校正,20 段,L/R 独立
preset type="ieq" + ieq-amount 杜比音色曲线,按百分比混入(实测只有 5–11%)
regulator-tuning 每段限幅阈值(喇叭保护)
volmax-boost 静态响度增益
volume-leveler / dialog-enhancer / virtualizer 动态 / 对白 / 虚拟环绕,只能近似

nabu 的曲线在 469 Hz–2.25 kHz 是一串 −7…−9 dB 的坑,本质是减法校正,不是"增强"。另外 ieq_balanced/warm/detailed 三条曲线在 agate/fuxi/marble/zircon 四台机上完全一致,说明它们是杜比 bundle 的通用音色,逐机差异在 audio-optimizer-bands(有的机型这一段全是 0,校准被放进 HAL/DSP 二进制)。

踩过的坑:滤波器级联 ≠ 曲线

第一版把 20 个频点直接映射成 20 个 Q=1.2 的 peaking 滤波器。频段只隔 1/3–1/2 倍频程,每个滤波器同时也在削邻居,级联后:

  • 曲线上 1 kHz = −7.6 dB
  • 实际级联 ≈ −20 dB
  • 整条链路平均 −12 dB(满幅粉噪逐样本跑 biquad 仿真验证)

为了把响度找回来又加了 +4 dB 补偿增益——这就是"音量 50% 以上炸麦"的来源:补偿增益把信号重新推回功放(CS35L41)的保护边缘。

教训:点值曲线要么用 FIR,要么拟合成少量宽滤波器再级联;不能把每个点当成独立 biquad 直接串起来。

修法:9 段宽滤波 + 零补偿 + 保险限幅

  • 最小二乘把曲线拟合成 9 个 Q=1.0、一倍频程间距的 peaking(63/125/250/500/1k/2k/4k/8k/16k),拟合误差 ±2 dB
  • 不加补偿增益;EQ output-gain = −2 dB 留余量
  • 限幅器只当安全网:−1 dBFS、5 ms lookahead、20 ms release,正常播放不动作
  • 60 Hz 高通,限制小板喇叭的低频行程

仿真(满幅粉噪):peak −6.0 → −14.5 dBFS,rms −18.5 → −25.7 dBFS。100% 音量也不会超过直通路径,从原理上不会再炸。

代价:同音量下比直通轻约 7 dB,靠音量旋钮补。

现状

组件 位置
预设 ~/.local/share/easyeffects/output/MIUI-Dolby-Pad5-v2.json
开机自启 ~/.config/systemd/user/easyeffects-dolby.service(enabled)
启动后加载预设 + 切默认 sink ~/.local/bin/ee-default-sink.sh
自动加载配置 ~/.local/share/easyeffects/autoload/output/alsa_output.platform-sound.HiFi__Speaker__sink:Speaker playback.json
systemctl --user status easyeffects-dolby.service
systemctl --user restart easyeffects-dolby.service
systemctl --user disable --now easyeffects-dolby.service   # 不要了

easyeffects -b 1    # 全局旁路,A/B 对比
easyeffects -b 2    # 恢复
wpctl set-default 48   # 临时把默认输出切回扬声器(绕过 EE)

想更响

在 EasyEffects 里加 Limiter → Input gain,每次 +2 dB,加到开始炸就退一格。不要靠改 EQ 抬增益——那等于把级联过深的问题再引回来。

第 5 部分 · 亮度与音量同步

5.1 waydroid-sync:组件、主控与音量映射

让 Ubuntu 与 Android 的亮度、音量互相跟随。

组件

项 位置
守护进程 ~/Apps/waydroid-sync.py
开机自启 ~/.config/systemd/user/waydroid-sync.service
变更日志 ~/.local/state/waydroid-sync.log(同时进 journal)
systemctl --user status waydroid-sync
systemctl --user restart waydroid-sync
systemctl --user disable --now waydroid-sync     # 不要了就关掉
journalctl --user -u waydroid-sync -f            # 实时看同步事件

音量:不能 1:1 对齐

两端音量是相乘关系:

实际听到 ≈ 宿主音量% × 安卓媒体音量%

所以「宿主 50% + 安卓 50%」实际只有 25%。1:1 同步会让音量莫名其妙变小。

解法是把非主控端钉住,主控端的数值就等于实际音量。

主控端由脚本顶部常量选择:

MASTER = "auto"      # 默认:谁被拖动谁生效,另一端归满——两边都能正常调
HOST_PIN = 100.0     # 固定主控模式下钉住的一端

# MASTER = "android"  # 安卓固定主控(宿主钉满)
# MASTER = "host"     # 宿主固定主控(安卓钉满)

默认 auto:你动哪边,哪边就保持住;另一边自动归满,保证"两端乘积 = 你这次调的数值"。

操作 结果 实际音量
宿主拖到 35% 宿主停在 35%,安卓归 100% 35%
安卓拖到 5/15 安卓停在 33%,宿主归 100% 33%

代价是没被调的那一侧会显示 100%。这在相乘结构下无法避免——两个滑块不可能同时显示真实值,总有一端要让位。

如果希望某一端永远固定(例如只在安卓里调音量),可以切换成 "android" 或 "host":


"auto"(默认) "android" "host"
安卓滑块 可用 永远满格,动不了 拖动后弹回
宿主滑块 可用 拖动后弹回 可用
通知/闹钟突发 哪端归满就哪端可能响 可能很响 被宿主压低,最安全

实测教训:钉死某一端(不管哪一端)都会让那边的音量条失效。"android" 模式下的原话是"没法把音量调大了,默认就是满的";"host" 模式下则是"宿主这边的应用都不能调音量了"。所以默认用 auto。

启动归一化

开机时两端可能都在半路(各 60%,实际 36%)。守护进程启动时把乘积算清并搬到主控端:

[19:50:45] 启动归一化:实际音量 53% → 安卓 53% / 宿主钉 100%

响度不变,只是把账本统一到主控端。

一个无法避免的副作用

在安卓里拖音量条,松手后会弹回满格(因为真正被调的是宿主音量)。这是相乘结构决定的:两个滑块不可能同时显示真实值。

亮度

层级 来源 说明
1 应用内窗口亮度 读 SurfaceFlinger 的 displayBrightness 覆盖值,映射到宿主背光
2 Android 全局设置 screen_brightness(0–255) 下拉快捷面板、设置里的亮度条
3 宿主 backlight(ktz8866-backlight,0–2047) GNOME 滑块、键盘快捷键

应用内调亮度时,SurfaceFlinger 会设置覆盖值,守护进程读到后直接写宿主背光;退出应用后覆盖消失,自动回落到全局设置同步(并做基线重对齐,避免跳变)。

# 查看某个 app 是否设置了窗口亮度
adb shell 'dumpsys SurfaceFlinger | grep -m1 displayBrightness'
#   displayBrightness=nullopt   → 没有 app 在控制
#   displayBrightness=0.35      → 有 app 设了 35%

注意:不是所有播放器都用窗口亮度。有的(如部分 B 站版本)用半透明黑色遮罩"调暗",那种在 SurfaceFlinger 里没有任何数值可读,无法桥接——因为它只是往画面上盖了一层黑,不改任何亮度参数。

另外,Waydroid 没有虚拟背光,Android 的亮度设置本身不会让画面变暗;现在它是通过同步去驱动宿主 backlight,才产生实际效果。

关键实现细节

  • 回读后存基线:Android 的媒体音量上限不是固定的——有时是 15,音量保护策略生效时会钳到 10。写完必须回读真实值存起来,否则会出现"设了又改回去"的振荡
  • 上限必须动态解析:cmd media_session volume --stream 3 --get 返回 volume is N in range [0..MAX],要按返回的 MAX 换算百分比。写死 10 会把拖到顶算成 150%,宿主被钳到 100% 并莫名跳满(这个坑踩过一次)
  • 忽略"被应用恢复到满格":安卓侧被钉在 100%,有些应用/系统会自己把音量恢复满格。这类变化要忽略,否则会被误判成用户调节,导致宿主突然跳到 100%
  • 按变化方向判断:只有一侧变了、另一侧没变才触发同步,两边都变就都不动
  • adb 自愈:容器重启后 adbd 可能没开,守护进程每 30 秒最多尝试一次 sudo waydroid adb
  • 容器没起时不报错:只记一行日志,等待容器就绪

第 6 部分 · 桌面工具链

6.1 看 Markdown:现状与结论

场景 现状
双击 .md 用 org.gnome.TextEditor.desktop 打开,纯文本,没有渲染
查当前默认程序 gio mime text/markdown
改默认程序 xdg-mime default <desktop-id> text/markdown
要渲染着看 用 dsh 的文档预览(见 6.2),工作区外的文件见 6.3

不要装 apostrophe。 apt 里是 3.4(2022 年的代码),在 Ubuntu 26.04 上直接坏:ApostropheTextView 的 markup 控制器没初始化成功,每次布局回调都抛异常,窗口永久「未响应」,正文永远不渲染,连 WebKit 子进程都没起来。日志特征:

apostrophe/text_view.py:327 in _on_size_allocate
    self.markup.update_margins_indents()
AttributeError: 'ApostropheTextView' object has no attribute 'markup'

2026-11-03 装过一次确认无解,已 apt remove --purge 卸载并把 mime 默认还原。真要个桌面编辑器再另找(Qt 系的 ghostwriter / retext 没这个问题),但日常看文档用 dsh 就够。

6.2 dsh Web GUI:文件树与文档预览

启动 dsh web,浏览器打开它打印的地址(本机是 http://127.0.0.1:3080)。

  • 右侧栏 →「文件」面板:树根是当前会话的工作目录(默认 ~/文档/deepseek-harness/default-workspace),只能往下,不能向上浏览。这是设计限制:Host 的目录列举按会话根做包含检查,不是配置项。
  • 点任意文件 → 开一个「文档预览」标签,内容由浏览器渲染,不依赖本地编辑器。支持 markdown(表格、代码块、相对路径图片、YAML frontmatter)、代码、图片、PDF、docx/pptx(本地转 PDF)、xlsx/csv、HTML。
  • 预览按页懒加载、文件改动自动刷新,所以改文档时不用手动重载。
  • 注意:读取接口本身不限制工作区,限制的只有「列目录」。所以既有文件能被预览,只是文件树里走不到。

6.3 自写插件:@local/dsh-open-file

在「文件」面板标题栏加一个按钮(箭头进文件夹的图标),弹出系统文件选择框、默认定位 ~/桌面,选中的文件直接用内置预览打开,不受工作区限制。

项 值
源码 ~/文档/deepseek-harness/default-workspace/plugins/dsh-open-file/
包名 @local/dsh-open-file
Host 半边 index.js:注册 GET /dsh-open-file/pick,spawn zenity --file-selection
Client 半边 client.js:往 sidebar.right.tab.files.actions 注册按钮,拿到路径后 openResource
起始目录 读 XDG_DESKTOP_DIR(= ~/桌面);可用环境变量 DSH_OPEN_FILE_START 覆盖
安装 plugin_manager 的 install_bundle,target 传上面那个绝对路径,装完刷新页面
卸载 plugin_manager 的 remove_bundle,target 传 @local/dsh-open-file

工作方式:Host 只负责「弹框、把你亲手选的那个绝对路径返回」,Client 把路径包成 dsh-resource://file/session/<会话id>//home/neko/桌面/xxx.md 再交给内置预览(首部 // 表示绝对路径)。安全边界:

  • 路由只接受 GET,只接受 Sec-Fetch-Site: same-origin(跨站 403),同一时刻只开一个对话框(409)。
  • 不接受任何路径参数,返回值只能来自对话框,伪造地址伪造不出文件。

装进的是 profile web(~/.dsh/profiles/web),pnpm add <目录> 建的是软链接,所以改源码目录里的文件就是改生效文件;改完刷新页面即可,模块版本没换就重启 dsh web。

6.4 装 dsh 插件的前置:pnpm

install_bundle 内部跑 pnpm add,本机最初没有 pnpm,会直接失败:

Command failed with ENOENT: pnpm add '...'
spawn pnpm ENOENT
sudo npm i -g pnpm@10   # 装到 /usr/local/bin/pnpm
pnpm --version          # 10.34.9

另外两条和这次相关的清理经验:

  • 不要用 apt autoremove 清空间。 本机有一批被标成「自动安装」但你在用的包(deepin-wine、exagear、caribou 等),autoremove 会一起带走。卸载单个包用 apt remove --purge <pkg> 就够。
  • 卸 apostrophe 时留下了 pandoc 等 4 个包共约 210 MB(当时作为依赖被拉进来)。pandoc 本身是通用文档转换工具,留着有用;确实不要就 apt remove pandoc pandoc-data python3-pypandoc python3-regex。

附录

A. 事故时间线(2026-11-02)

时间 事件
19:54:30 上一段 6h 会话干净关机
19:54:31 插电自动开机(off-mode-charge=0)
19:55:03 logind 收到电源键事件 → 干净关机
19:55:47 关机完成,1 秒后又自动上电
19:56:19 再次 poweroff.target 排队 → 19:56:57 关机
19:56:57 第三次启动时 slot-unbootable:a=yes、retry=0 → 停 fastboot
19:56:59 set_active a 后恢复启动
20:49:05 33W 头下 v_offset 被写坏,充电进入 fault 循环
21:11–21:13 尝试 rebind ln8000 → 内核 oops(21:13:36)→ 死机 → 重启
21:55 fastboot 测试:确认计数 5→4、set_active 回到 7
11/03 22:38 6~8% 低电量下再次进入坏状态(tcpm 0V、不充电),重插后恢复 −3.6A
11/04 00:33 99% 时拔线,v_offset 瞬时溢出(属正常瞬变),此后待机

B. 不要做的事

操作 后果
fastboot set_active b b 从未成功启动过,切过去大概率直接 fastboot
fastboot flash boot_a ... 覆盖 dualboot bootmanager,丢 Linux 引导
fastboot erase misc 已验证无效(槽信息不在这里)
fastboot oem lock / unlock 可能清数据
unbind/rebind ln8000 内核 oops 死机
刷比当前旧的固件 anti:1,可能硬砖
chown -R 整个 Waydroid /data keystore(1017)/media(1023) 属主被改坏 → 无限重启(见 3.5)
sudo waydroid session start DBus NoReply,管理器无响应(见 3.5)
改 init.waydroid.rc 加 setenv 容器起不来,必须改 init.environ.rc(见 3.5)
单独重启 PipeWire 容器里的 pulse socket 变陈旧 inode,Android 立刻没声音(见 4.1)
apt install apostrophe 3.4 在 26.04 上崩溃(markup AttributeError),窗口永久无响应(见 6.1)
apt autoremove 会带走 deepin-wine / exagear 等仍在使用的包(见 6.4)

C. 命令备忘

# ---- 启动 / 槽位 ----
fastboot set_active a && fastboot reboot
cat ~/.local/share/nabu/boot-count
nabu-boot-count reset

# ---- 充电 ----
cat /sys/class/power_supply/qcom-battery/status
cat /sys/class/power_supply/qcom-battery/current_now   # 6.14: 负=充电  6.17: 正=充电
cat /sys/class/power_supply/qcom-battery/capacity
journalctl -k -n 20 | grep -o 'v_offset=[0-9]*' | tail -1
timeout 20 udevadm monitor --subsystem-match=typec 2>/dev/null | grep -c port0-partner

# ---- Waydroid ----
waydroid session stop
sudo systemctl start waydroid-container
waydroid session start &
waydroid show-full-ui
waydroid status

# ---- 音频 ----
pw-metadata -n settings | grep -E 'force-quantum|min-quantum'
timeout 8 pw-top -b -n 3 | grep -E 'alsa_output|Waydroid'
wpctl status | sed -n '/Streams:/,/Video/p'

# ---- 文档 / dsh ----
gio mime text/markdown                      # 当前用什么打开 .md
sudo npm i -g pnpm@10                       # dsh 装插件的前置(只需一次)
dsh web                                     # 启 Web GUI,浏览器访问它打印的地址

D. 参考


E. 局域网访问 DSH(黑屏救援用)

systemctl --user status dsh-lan        # 绑定 192.168.31.41:3081 的实例
cat ~/桌面/dsh-局域网地址.txt          # 带 token 的最新 URL
  • token 每次重启都会变,URL 以桌面那个 txt(或 ~/.local/share/nabu/dsh-lan-url.txt)为准
  • 所有设备必须连同一个实例:DSH 的会话同一时间只能被一个进程驱动, 连到别的实例会报"当前会话已被占用"
  • 只监听局域网 IP,别在路由器上映射到公网(URL 等价于以 neko 身份执行命令)

F. 电源键与休眠显示

行为(2026-11-04 实测定稿):

操作 结果
亮屏时按电源键 挂起(systemctl suspend)
熄屏时按电源键 点亮(PowerSaveMode=0 + 解除背光 powerdown)
从挂起恢复 2 秒后强制点亮一次(兜底)

为什么这么绕:这块屏有两个坑,单独用系统默认逻辑都会踩到——

  1. GNOME 的 power-button-action 默认是 suspend,按电源键唤醒时同一个按键事件又触发一次挂起,表现为"按了没反应、要多按几次";
  2. /sys/class/drm/card0-DSI-1/dpms 是 只读 的(-r--r--r--),root 也写不进去;真正控制显示开关的是 mutter 的 PowerSaveMode(0=亮,1=暗,3=关)。
  3. 偶尔从挂起恢复后,mutter 认为屏亮着、但面板没重新初始化,屏幕实际是黑的。

当前机制:

组件 作用
gnome power-button-action = 'nothing' 让 GNOME 不抢电源键
nabu-wake-display(system 服务) 监听电源键 → 按 dpms 状态决定挂起还是点亮
nabu-display-on-resume(system 服务) 每次 resume 后 2 秒强制点亮
nabu-resume-probe(system 服务) 记录每次唤醒的 dpms/背光状态到 /var/log/nabu-resume.log

手动操作:

# 强制点亮(黑屏时最有用)
sudo /usr/local/bin/nabu-wake-display --resume

# 看电源键/点亮动作的日志
journalctl -t nabu-wake-display -n 20

# 看每次唤醒的显示状态
sudo tail -20 /var/log/nabu-resume.log

撤销回系统默认:

sudo systemctl disable --now nabu-wake-display nabu-display-on-resume
gsettings set org.gnome.settings-daemon.plugins.power power-button-action 'suspend'

注意:撤销后就会回到"按电源键可能唤不醒、要多按几次"的老状态。

G. 待分析:合上盖子偶尔不触发息屏(2026-11-04 记录)

现象:合上平板套时,有时正常挂起息屏,有时合好几次都没反应(用户原话:"不太灵敏");打开盖子时唤醒倒是正常。

已确认的事实:

  • 霍尔传感器工作正常,内核会报 EV_SW / SW_LID:
    14:04:04  gpio-keys type=5 code=0 value=1   ← 合上
    14:04:10  gpio-keys type=5 code=0 value=0   ← 打开
  • logind 也正常处理:
    14:04:04  systemd-logind: Lid closed.
    14:04:04  systemd-logind: Suspending...
    14:04:05  PM: suspend entry (s2idle)
  • 失败的几次完全没有 SW_LID 事件(60 秒抓取 0 条),说明不是 logind/GNOME 的问题, 而是磁铁→霍尔这一层偶尔没被触发。
  • /etc/systemd/logind.conf 里 HandleLidSwitch 是注释状态(默认 suspend),未被改动。

可能原因(待验证):

  1. 套子里的磁铁位置/强度不够,合上时霍尔电压没跨过阈值(换了保护套可能就正常)
  2. gpio-keys 的 debounce 或阈值配置
  3. 合上速度太快/角度不对,磁铁没到位

下次排查用的命令:

# 实时看霍尔事件(合上/打开盖子)
sudo timeout 60 python3 ~/.local/share/nabu/lidcap.py

# 看 logind 有没有收到
journalctl -f | grep -iE 'Lid|suspend entry|suspend exit'

# 历史记录
journalctl | grep -c 'Lid closed'

临时对策:合上后如果没息屏,按一次电源键手动挂起;或者把自动挂起时间调短。

已保存的抓取数据:~/.local/share/nabu/lid-capture-20261104.log

本文作者:赵苦瓜      文章标题: 小米平板5-Linux使用与排障手册
本文地址:https://blog.zhaokugua.com/?post=121
版权声明:若无注明,本文皆为“赵苦瓜のBlog~”原创,转载请保留文章出处。
AI2026-11-02 21:25
# fastboot 测试计划(A/B 重试计数)

> 进入 fastboot:关机 → 按住「音量减」→ 插 USB 线。
> 每条命令都用 `2>&1`,因为 getvar 的结果走 stderr。
> 全程只需要 **set_active / oem off-mode-charge**,不要 flash、不要 erase、不要动 b 槽。

## 0. 先存一份完整快照

```bash
fastboot devices
fastboot getvar all 2>&1 | tee all_before.txt
```

## 1. 只读基线(全部安全)

```bash
fastboot getvar current-slot        # 期望 a
fastboot getvar slot-count          # 期望 2
fastboot getvar slot-retry-count:a  # 关键
fastboot getvar slot-retry-count:b
fastboot getvar slot-unbootable:a
fastboot getvar slot-unbootable:b
fastboot getvar slot-successful:a
fastboot getvar slot-successful:b
fastboot getvar battery-voltage
fastboot getvar battery-soc-ok
fastboot getvar off-mode-charge
fastboot getvar charger-screen-enabled
fastboot getvar anti
fastboot getvar crc                 # devinfo 的 CRC,写入后会变
```

把这些值抄下来(下面表格)。

## 2. 关键实验:计数到底怎么扣

1. fastboot 下记录 `slot-retry-count:a` = **N**
2. 长按电源正常开机 → 进 Ubuntu(等完全启动)
3. 关机,再按住音量减插线进 fastboot
4. 再读 `slot-retry-count:a` = **M**

| 结果 | 结论 | 对策 |
|---|---|---|
| M = N-1 | 每启动一次扣 1,约 7 次后进 fastboot | 每次开机后 `fastboot set_active a` 补满;或减少重启 |
| M = N | 只有"启动失败/快速断电"才扣 | 正常用不会耗尽,避免"插电自动开机→立刻断电"循环 |
| M > N 或异常 | 计数被重置/读错 | 记录原始输出再分析 |

第 3 步后顺便检查:`fastboot getvar slot-successful:a`
- 若变成 `yes`:系统有上报成功,问题在别处
- 若是 `no`:Ubuntu 侧没有 markBootSuccessful(与之前的结论一致)

## 3. 可能"关掉计数/避免计数流失"的命令(按风险从低到高)

### 3.1 重置计数(已证实有效)

```bash
fastboot set_active a
fastboot getvar slot-retry-count:a   # 期望回到 6 或 7
fastboot getvar slot-unbootable:a    # 期望 no
```

### 3.2 关掉"插电自动开机"(间接避免计数流失)

```bash
fastboot oem off-mode-charge 1
fastboot getvar off-mode-charge      # 期望 1;若报错说明不支持,忽略
```

验证:关机 → 插线 → 应停在充电/黑屏,而不是自动启动。
恢复:`fastboot oem off-mode-charge 0`

### 3.3 充电屏(可选,可能不存在)

```bash
fastboot oem charger-screen-enabled 1   # 报错就跳过
fastboot getvar charger-screen-enabled
```

### 3.4 设备信息(只读,帮助判断)

```bash
fastboot oem device-info            # 有的 ABL 支持,有的报错
```

### 3.5 不要碰

- `fastboot set_active b`(b 从未成功启动过)
- `fastboot flash boot_a ...`(boot_a 是 dualboot bootmanager)
- `fastboot erase misc`(已验证无效)
- `fastboot oem lock/unlock`(可能清数据)

## 4. 记录表

| 项目 | 测试前 | set_active a 后 | 一次正常启动后 |
|---|---|---|---|
| current-slot | | | |
| slot-retry-count:a | | | |
| slot-unbootable:a | | | |
| slot-successful:a | | | |
| off-mode-charge | | | |
| crc | | | |

## 5. 安全网

万一测到计数归零、进不了系统:**`fastboot set_active a` → `fastboot reboot`** 就能回来。
所以这次实验风险很低,只要记住这一条。

返回顶部    首页    后花园  
版权所有:赵苦瓜のBlog~    站长: 赵苦瓜    程序:emlog   鲁ICP备20030743号-4   鲁公网安备37048102006726 萌ICP备20260614号    sitemap