小米 5 移植 postmarketOS 适配记录
小米 5(xiaomi-gemini)是 2016 年的骁龙 820 机型,官方 LineageOS 一直维护到现在。
一直想试试 postmarketOS 能把一台老手机折腾到什么程度,于是从 10 月 29 日开始,前后花了三天时间做移植适配。
最终结果:系统能正常进入 Phosh 桌面,USB 网络、Wi-Fi、蓝牙、显示触摸、传感器等基本硬件可用,但相机、扬声器、蜂窝网络没有修好,10 月 31 日刷回了 LineageOS。本文整理整个过程和踩过的坑。
本文由 AI 根据适配过程中的文档、验收记录和日志整理编写,内容经过人工核对。
设备与方案
| 项目 | 内容 |
|---|---|
| 设备 | Xiaomi Mi 5(xiaomi-gemini,MSM8996),64GB 存储 |
| 内存 | 3GB 改造为 6GB(Android 下使用正常) |
| 原系统 | LineageOS 22.2 / Android 15,bootloader 已解锁,保留 Lineage Recovery |
| 目标系统 | postmarketOS v26.06 + Phosh,内核 Linux 6.19.5(msm8996-mainline) |
| 构建工具 | pmbootstrap 3.12.1 |
刷机策略上只写 userdata 和 boot 两个分区,recovery、system、基带、bootloader 全部保留,留一条后路。userdata 内部是嵌套 GPT,逻辑扇区 4096 字节,根分区首次启动时自动扩展到剩余空间:
1 | userdata |
装好后根分区可用空间 47.7G,对手机来说非常充裕。
构建镜像
pmbootstrap 的关键初始化参数:
1 | device = xiaomi-gemini |
整个构建流程封装成了一个脚本:install → export → 生成修正版 boot → 展开 sparse 镜像供校验,结束时自动 pmbootstrap shutdown。
构建时踩到的坑主要是网络:国外镜像 SSL 超时、下载中断,高通固件 linux-firmware-qcom 最终改从清华 Alpine 镜像获取,核对包名、字节数后用 pmbootstrap 内置的 Alpine 公钥验签通过;另外补取了 19 个 x86_64 工具包,最后用缓存离线打包成功。
宿主机还修了一处 /etc/hosts 的 127.0.1.1 别名不一致问题(sudo 告警会污染 pmbootstrap 输出),全程没有通过 apt 装任何全局软件包。
刷机:停在 MI 标志
1 | fastboot flash userdata images/xiaomi-gemini.img # 耗时 269 秒 |
刷完直接卡在 MI 标志,进不了系统。
原因:ramdisk 和内核加载范围重叠
原始配置中内核加载地址是 0x80008000,ramdisk 地址是 0x81000000,两者间隔只有 16MiB。
而 Linux 6.19.5 的 arm64 Image 头声明的运行时镜像大小为 28,049,408 字节,从 0x80008000 一路占用到 0x81ac8000,已经越过 ramdisk 地址,启动时直接重叠。
修复很简单,把 ramdisk 挪到 0x84000000:
- 先做了一个只改 ramdisk 地址的临时镜像,用
fastboot boot不刷写验证,能正常进桌面。 - 生成正式的
boot-fixed.img刷入,正常重启通过。 - 在 initramfs 的
hooks-cleanup中加幂等钩子,向/etc/deviceinfo写入deviceinfo_flash_offset_ramdisk="0x04000000",这样后续内核/initramfs 更新也能读到覆盖值。
这是整个适配中最”一击致命”的问题,也最适合反馈上游设备包。
硬件适配
能进桌面只是开始,真正的工作量在硬件 bring-up。每个子系统都要单独排查,下表是最终结果:
| 硬件 | 现象 | 解决方法 | 状态 |
|---|---|---|---|
| USB | QUSB2PHY pll lock failed,DWC3 probe -16,UDC 为空 |
开机延后重探测 6a00000.usb,配 NCM/DHCP,再启专用 SSH |
规避成功,PHY 根因未解 |
| Wi-Fi | PCIe Phy link never came up,只有根端口 |
pcie_aspm=off pci=nomsi + 链路迟到后自动 rescan 服务 |
自动恢复,建链根因未解 |
| 蓝牙 | hci0 存在但 BlueZ 无控制器 | 用 persist 中的工厂地址做 MGMT public-address 配置 | 可用,未测配对 |
| GPU | 固件早期缺失,两次 60 秒 fallback 等待 | initramfs 预置三个 A530 固件 + 关闭 sysfs fallback | 加速性能未验证 |
| 声卡 | 约 101 秒 SlimBus NULL+4 Oops |
回移上游修复为独立模块,原模块黑名单 | 声卡注册,扬声器无声 |
| 传感器 | sns.reg 固件请求失败 |
从 persist 复制本机校准数据到 /lib/firmware |
五类 IIO 出现 |
| 调频 | CPU 0 早期 RCU stall(sugov:0) |
先关调频保稳定,后改 userspace 启动 + TuneD 排序 | 已恢复,根因未定位 |
USB:延后重探测
手机 USB 网络完全不可用,电脑端 lsusb 看不到设备。日志显示 PHY PLL 锁定失败、DWC3 probe 返回 -16。
没有直接去啃供电域/时钟根因,而是做了个运行时变通:开机约 60 秒后向 /sys/bus/platform/drivers_probe 重新探测 6a00000.usb,首次重试即成功,UDC 出现后 NCM 网络枚举、SSH 登录验证通过。
配套做了三个 systemd 服务:USB 重探测、专用 SSH(只监听 172.16.42.1:22,通用 sshd 禁用)、以及后续的启动顺序编排。
Wi-Fi:链路晚到,但没人补扫
这是最有意思的一个问题。PCIe 早期链路训练超时后总线上只有根端口,没有 Wi-Fi 端点(QCA6174,168c:003e),wlan0 根本不出现。pcie_aspm=off pci=nomsi 能提高成功率,但不能保证每次启动。
关键证据来自一次失败启动的后续日志:稍后读取 Link Status 已经是 0x3011(DLL Link Active 已置位),说明链路其实建起来了,但总线仍然是空的——早期扫描落空后,没有任何代码去补扫。
对照源码确认了原因:dw_pcie_host_init() 忽略 dw_pcie_wait_for_link() 的超时结果继续 pci_host_probe();而 pcie-qcom.c 只有在设备树提供名为 global 的中断时才启用 Link Up IRQ 路径,MSM8996 的 PCIe0 设备树只列了八个 MSI 中断,这个配置下链路迟到就永远没人发现。
于是写了个启动服务 mi5-wifi-startup.service:链路未就绪时最多等 120 秒,DLL Link Active 置位后对空下游总线做标准重扫描,最多三次。安装后连续五次普通重启全部通过,其中两次自然复现早期链路超时,都由服务自动恢复(约 76 秒 rescan,78 秒出现 wlan0,86 秒自动联网,网关 ping 3/3)。
蓝牙:工厂地址
bluetoothctl list 为空,管理接口显示控制器缺 public address。工厂地址在 persist 分区的 wlan_bt/bt.mac(6 字节小端),复制到本地后通过 MGMT Set Public Address 配置,BlueZ 立刻 Powered=yes,扫描到 12 个设备,且 Wi-Fi 同时在线。
persist 全程只读,地址副本存手机 /etc/mi5-hardware/ 下,权限 0600。
GPU:60 秒的固件等待
启动日志里 a530_pm4.fw 请求失败后进入 sysfs fallback,每次等 60 秒超时,前后浪费两分多钟。initramfs 里根本没有这三个 A530 固件。
把固件加入 initramfs 预置清单,并通过内核参数 sysctl.kernel.firmware_config.ignore_sysfs_fallback=1 关闭 fallback,早期固件 13.8 秒加载完成,显示触摸正常。
SlimBus:Recovery 里捞出完整 Oops
声卡这块最曲折。系统运行约 101 秒时 qcom_slim_ngd_runtime_resume 出现 NULL+4 Oops。
问题是 Recovery 4.4 内核只读挂载不回放事务,读不到新日志;读写挂载又被 orphan_present 特性拒绝。
最后把手机自带的 e2fsck 1.47.4 和依赖库复制到 Recovery /tmp,在未挂载的 loop 上仅做 journal_only 回放,才取到完整调用栈。
修复本身是回移上游提交 2a9d50e9ea406 及生命周期调整(额外把 ngd->base 初始化提前到子设备注册之前),用 clang 18 编译成独立模块 slim_qcom_ngd_ctrl_fixed,原模块黑名单隔离,启动服务延后加载。
加载后 WCD9335 和 gemini 声卡正常注册,多次重启无 Oops。模块校验内核版本和原模块哈希,不匹配直接拒绝加载。
传感器:每台机器的校准数据不同
sns.reg 是高通传感器的注册/校准数据,每台机器都不同。从 persist 分区只读复制本机的 25KB 数据到 /lib/firmware/qcom/sensors/sns.reg-xiaomi,gemini 后,accel、gyro、mag、prox、pressure 五类 IIO 接口全部出现,加速度计读到了实际三轴样本。
调频:先保命,再慢慢恢复
早期启动反复出现 CPU 0 的 RCU stall,调用栈全在 sugov:0(schedutil 的工作线程)做时钟切换的路径上。
诊断阶段先用 cpufreq.off=1 关掉整个 CPUFreq 子系统,系统稳定了,代价是 CPU 锁在约 1.04GHz。
后来逐步恢复:临时镜像验证 cpufreq.default_governor=userspace 可以正常变频后,发现是 TuneD 的 powersave profile 会把 governor 接管回 schedutil。
最终方案是启动先用 userspace,通过 drop-in 让 TuneD 等蓝牙恢复服务完成后才启动,再由它接管 schedutil。持久化后普通重启、短时四核负载(policy0/policy2 分别达到 1.59GHz/2.15GHz)均通过。
这属于启动时序修复,底层时钟/RCU 根因没有闭环。
诊断方法
回头总结,这套流程能走下来主要靠几个原则:
- 每次只改一个变量。 所有试验先用
fastboot boot临时启动验证,不刷写分区;只有实机验收通过的方案才写入 boot,且刷前先完整备份原 64MiB boot 分区。 - 证据先于重启。 进 Recovery 第一时间保存 pstore,再考虑重启,否则日志会被覆盖。
- 每个镜像记 SHA256。 试验镜像、回退镜像、当前 boot 全部留哈希,避免”到底刷的是哪版”的混乱。
- 失败也是结果。 扬声器两代候选(一版无声、一版导致 UFS 超时)都明确否定并记录,防止将来重复踩坑。
过程中也有哭笑不得的时刻:Fastboot 返回 Booting OKAY 只代表命令被接受,手机屏幕停在 Fastboot 画面也不代表内核没跑——完整 pstore 证明它其实进了内核然后卡死。
没修好的部分
- 相机:设备树里根本没有相机传感器管线配置,
/dev/video0是 Venus 编解码器。需要先确认前后摄型号才能补管线。 - 扬声器:TFA9890 功放能绑定,但播放无声,DSP 侧有 COPP 关闭和 q6asm 超时;补 SD line 的候选镜像反而导致 UFS 链路断裂,已否定。
- 蜂窝/SIM:基带实际读到了卡(QMI 查询
present/ready),但 ModemManager 因Failed to find a net port in the QMI modem拒绝创建 modem。当前内核缺少 MSM8996 的 IPA 2.5 数据通路支持,这是个需要真正移植驱动的大坑。 - 其他:蓝牙配对/A2DP、GPU 加速性能、部分传感器(除加速度外仅确认枚举)、通话、GNSS、NFC 均未验证。
结局
10 月 31 日决定暂停移植,恢复日常使用的手机。备份好 postmarketOS 的配置、校准数据和 boot 分区后,格式化 userdata/cache,sideload 官方最新 LineageOS 22.2 / Android 15(2026-10-28 构建),安装 status 0,boot 读回哈希与官方一致,正常进入桌面。
项目目录、镜像、诊断日志都保留在电脑上,也整理了一份可上游化的成果清单(ramdisk 地址修复证据最明确,其次是 SlimBus 回移反馈和 Wi-Fi 问题报告),但尚未提交。
小结
postmarketOS 对 msm8996 的支持已经能让老机器进桌面,但”能进桌面”和”日常可用”之间隔着一整套硬件 bring-up:USB、Wi-Fi、蓝牙、音频、传感器,每个子系统都要单独过一遍,而且多数问题的解法是带条件的规避而非根治。
对想尝试的人的建议:先确认设备页状态、准备好 Recovery 和完整备份,然后享受过程——这类折腾的乐趣大概就在于此。