Communitygithub.com

angelwzr/linux-phone-porting

一个帮助您将主线 Linux 移植到安卓手机上的编码智能体技能。

linux-phone-porting 是什么?

该技能旨在自动化并加速将主线 Linux 移植到 Android 手机上的流程。它严格遵循一个系统化的流程,从备份手机分区的关键第零阶段开始,这对于保存 IMEI 和股票 DTB(设备树 blob)等设备唯一数据至关重要。随后是第一阶段:在实施修复之前实时诊断问题,并指导用户使用 pstore、ramoops 和 USB ACM 控制台等机制收集可靠的崩溃日志。然后,技能进入第二阶段:深度研究,综合来自主线内核、OEM 下游内核源码、postmarketOS、Halium/UBports、Mobian 和 NixOS 等多种来源的信息,以查明故障或确定新功能所需的内容。该技能专门针对像 Claude Code 这样的编码智能体平台设计,将较为繁琐的、基于实证的 Linux 移植过程编码为一种可重复的、故障安全的且可验证的工程实践,将每次迭代的成本严格限制为“一次启动和一次日志记录行”。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add angelwzr/linux-phone-porting

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Linux 手机移植

在正在进行主线化适配的手机上进行调试迭代,其成本包括一次构建(build)、一次刷写(flash)和一次启动(boot),耗时约 5-10 分钟。而且,盲猜式的修复尝试往往以失败告终,远不如精准定位的修复有效。请严格遵守以下步骤顺序。

0. 设置移植环境

只需执行一次,在首次刷写之前完成。将其设为第零阶段而非简单的准备工作,有两个原因:原厂系统是你将针对此主板拥有的、权威性最高的研究来源;并且,一旦你开始覆盖分区,其部分内容将变得不可读取。

首先明确移植目标,因为后续的所有答案都取决于此:

  • 确切的设备身份——营销名称、代号、SoC 以及区域或存储版本。同一营销名称的兄弟版本通常会在屏幕、触控控制器或调制解调器上存在差异,针对错误版本研究的修复方案可能看似合理但最终会失败。
  • 目标发行版和初始化系统——postmarketOS、Mobian、NixOS、纯 Debian 或其他。这决定了打包方式、镜像布局以及第二阶段中最接近的源码来源。
  • 启动拓扑——A/B 或非 A/B 分区布局、槽位布局以及引导加载程序的锁定状态。仅限解锁的引导加载程序。 如果引导加载程序已锁定,请停止:本技能不提供解锁引导加载程序的方法,也不提供解锁步骤——请引导用户参考其设备 OEM 的官方说明,并在设备解锁后继续操作。
  • 恢复模式——是否安装了自定义恢复模式。这不是强制要求,而是一种方便进行备份和恢复的工具。

然后,对除用户数据(userdata)之外的每个分区进行完整备份:

  • 跳过用户数据分区:它占据了大部分空间,且是用户的数据,也是你可以承受丢失的唯一分区。
  • 备份设备唯一分区,切勿公开。 Modem NV/EFS、persist 及其校准兄弟分区包含此特定设备的 IMEI、无线电校准和传感器校准信息。它们无法从互联网上的任何镜像中复现,损坏它们的移植会导致手机无法注册网络。将它们视为既重要又私密的数据。
  • 对每个镜像进行哈希处理,并将哈希值与分区表一起记录。稍后你将需要回答“设备上的内容是否仍是我备份的内容?”,只有哈希值才能给出答案。
  • 在首次风险刷写之前(而非之后)验证备份是否确实可恢复。

备份作为研究资料的价值(超越恢复用途):

  • 原厂的 DTB——此主板设备树的真实来源,也是当兄弟 SoC 的 dtsi 与之冲突时的仲裁者。
  • 固件 blob 及其加载顺序——供应商软件栈实际提供的固件以及它期望在何处找到它们。
  • 供应商内核命令行和启动镜像布局——偏移量、头部版本以及原厂内核接收到的参数。
  • 确切的内核版本字符串——来自原厂 ROM 的 uname -a 或 /proc/version(在它仍然可读时获取,稍后也可从备份的启动镜像中解包)。它是第二阶段中选择正确的 GPL 发布版 OEM 源码版本的指纹标识;供应商会发布多个构建版本,它们各不相同。
  • 供应商配置——描述主线驱动必须满足的接口的传感器、调制解调器和 HAL 配置。

最后,设定本次会话其余部分的工作惯例:在主机端创建一个目录,用于按启动次数累积每个启动的日志和 pstore 记录,并记录已知有效的恢复路径。如果已存在先前尝试的备份,请确认它覆盖了这些分区,并且其哈希值仍然有效,而不是想当然地认为它没问题。

1. 首先从运行中的设备收集证据

在更改任何内容之前,捕获失败运行的完整 dmesg。在复现问题之前启用相关的调试开关(例如 echo 0xffffffff > /sys/module/<driver>/parameters/debug_mask),因为二次复现需要再次启动。

证据渠道,按可靠性大致排序:

  • 通过网络或 USB 的实时 shell。 成本最低的渠道;用于设备仍然存活情况下的任何信息收集。
  • pstore。 一旦 systemd 启动,从 /var/lib/systemd/pstore/ 收集,而不是 /sys/fs/pstore/——systemd-pstore.service 在启动早期就会移动每条记录,因此空的 /sys/fs/pstore 并不能证明没有记录到崩溃。控制台区域通常只有一个槽位:在处理其他任何事情之前,先将其复制到主机,否则下一次崩溃会覆盖存档中的它。
  • ramoops。 视为有损。断电期间 DRAM 电荷保持能力会衰减——在某个设备上测量到约 6.5% 的比特错误,足以使区域头部的 ECC 校验失败并完全丢失一条记录。对记录进行模糊搜索。导致区域损坏的是断电时间,而不是你在切断电源前狂刷日志的时间,所以让一个死锁的设备尽可能长时间地记录(只要控制台区域能容纳),然后尽快断电并重新上电。在假设你必须匆忙行事之前,请检查区域大小:一个 4 MiB 的控制台区域在回绕前可以吸收数小时的看门狗(watchdog)刷屏日志。
  • 内核级 USB ACM 控制台(通过 CONFIG_U_SERIAL_CONSOLE=y 和内核命令行中的 console=ttyGS0)。这是唯一能在设备死锁后幸存的渠道,其一次性刷写成本在移植早期是值得的。一个用户空间的 ACM getty 无法在设备死锁后幸存——它需要一个可调度的 CPU,并且在设备通过 USB 枚举正常时也不会输出任何字节。这两个部分都是必需的:仅配置符号会使该功能处于非活动状态。

五个反复将推理转化为测量的规则:

  • 通过读取分区内容来确认实际刷写了什么,切勿依赖笔记或构建日志。使用 iflag=direct 进行读取,以使读取操作本身不会干扰您正在调试的内容。注意仅比较大小的陷阱——引导镜像会被填充到固定大小,因此两个不同镜像的等长大小不能证明任何问题。对有效载荷进行哈希处理。
  • 读取调试接口可能制造出症状。 DRM/GPU 崩溃状态节点是典型例子:在 GPU 活跃提交时采样一个节点,会合成了从未发生过的故障和恢复行(经测量:五次读取,五对故障/恢复,而未被观测时则为零)。在相信日志行之前,请检查您的观测本身是否产生了它。
  • 在解释映射的 errno 之前,捕获被拒绝的原始响应。 一个固件调用的映射错误可能是模糊的——EINVAL 可能意味着错误的编码或被拒绝的策略——直到调用包装器中的动态调试暴露了原始响应字:一个格式正确且携带明确拒绝的调用。这是策略问题,而非编码问题——判断结果相反,下一步操作也相反。在对每个固件拒绝进行分类之前,将其归类为:编码错误、策略拒绝或未找到。镜像规则:一个干净、格式正确的否定回复是正向证明——一个来自格式正确查找的“未找到”结果验证了编码并消除了计划中的一个分支,而无需触及加载器。
  • 一个与不受控条件赛跑的测量会返回一个看似合理但无效的数字。 一次基准测试在两秒内得分为 55:合成器持有 DRM master,每个场景都失败,该数字看起来真实,直到检查了该条件。有效的比较——132 → 485——需要为两次运行都停止合成器,并且仅在那一代内进行比较。在相信基准测试之前,请指出可能使其无效的条件,并验证它在每次运行中都成立。
  • 记录你撤回的规则。 移植过程中会迅速积累民间传说式规则,一条在一个硬件版本或一个缓冲区大小下正确的规则,在变化后就会变得具有误导性。将已推翻的版本与当前版本并排放置,并标明变化内容,这样下一次会话就知道哪些建议经过了压力测试。

值得预判的主机端陷阱:

  • timeout N ssh <device> '<cmd>' 只杀死本地客户端;远程进程继续运行。请随后执行显式的 kill 命令。
  • pgrep -f 和 pkill -f 通过相同的 ssh 路径运行时,会自我匹配它们自己的远程 shell。请过滤掉匹配器本身,或按 PID 匹配。
  • 当起始路径是符号链接时,find 需要 -L 参数,否则会静默返回空结果。
  • 任何重写命令输出的包装器或钩子——令牌过滤器、分页器、格式化器——都会破坏证据,有时是通过反转测试而不是明显破坏它。对于你将作为发现结果提交的任何内容,请使用原始命令。

2. 在实现之前进行研究——使用所有这些来源

研究总是在实现之前进行。研究所消耗的内容取决于你正在做什么:

  • 调查故障——阶段 1 的证据。首先捕获它;研究一个你实际上没有读取过的症状就像带着引文进行猜测。
  • 添加功能——一个之前从未涉及过的驱动、外设或子系统——阶段 0 的产物是设备数据。原厂 DTB、供应商配置和固件布局描述了该主板期望如何驱动该硬件,它们的权威性在此处与崩溃日志在调查故障时一样。

无论哪种情况,在编写任何代码之前:

  1. 推理子系统——确定哪个驱动、绑定或固件接口拥有该故障,或将要拥有新功能。你可用的技能已列在会话上下文中:现在扫描该列表,如果其中有内核开发或崩溃分析技能,请在继续之前阅读它——它的寄存器级和绑定细节正是此步骤所需的。仅在列表中未列出时才直接进行推理。
  2. 查找涉及的用户空间库和工具——当前文档,而不是回忆的 API。如果同一会话列表中有文档检索技能,请运行它,而不是从记忆中回忆签名。
  3. 跨所有六个来源家族进行网络研究(并行子代理在此处效果很好;如果列表中有网络研究技能,请优先使用它进行缓存,因为会话会重复读取相同的页面):
    • 主线 (Mainline)——lore.kernel.org、驱动源码、DT 绑定以及最接近的兄弟 SoC(相同家族或相同 RPM 代)的 dtsi。在界定任何“移植下游驱动”工作的范围之前,请扫视主线已经为该接口提供了什么:其导出的 API、是到达用户空间还是仅在内核内部、以及它的门控——机器许可名单、“由引导加载程序预加载”语义、内置而非模块(经测量:一次这样的扫视将一个 9400 行的下游移植工作缩减为一个约 300 行的探测模块,因为主线已经在许可名单后面内部拥有了该接口内核——计划变为驱动现有 API,而不是移植它)。
    • 下游供应商内核——GPL 发布的 OEM 源码。 GPLv2 要求 Android OEM 发布他们提供的内核源码,因此通常存在此设备的精确发布版本:OEM 的开源门户(三星、索尼、小米、Fairphone、一加/OPPO 和摩托罗拉都有)、他们的 GitHub/GitLab 镜像以及社区存档(如 XDA,当门户链接失效时)。选择版本字符串与阶段 0 的 /proc/version 匹配的压缩包——供应商会发布多个版本,它们各不相同。合规性质量参差不齐,因此将该源码树视为一个可挖掘的神谕——此确切设备的主板 dts 和 defconfig,以及树外供应商驱动(触控、面板、充电器、调制解调器粘合层)及其寄存器序列和固件握手指令——而不是假设它可以构建。当没有确切的发布版本时,兄弟设备的发布版本通常仍然包含 SoC dtsi。更好的是:直接从设备上提取的原厂 DTB(dd 未修改的启动槽,扫描 d00dfeed,dtc -I dtb -O dts)。这是此主板的真实来源,并多次纠正了错误的兄弟 SoC 猜测——包括一个兄弟 SoC 弄错但原厂 DTB 正确修正的 SMMU 流 ID。
    • postmarketOS——pmaports 设备包和 APKBUILD、合并请求、wiki 设备页面以及 SoC-mainline 树。
    • Halium / UBports——halium/android_device_* 树、hybris-boot 配置以及 UBports 设备移植。Halium 运行 Android 供应商软件栈而非主线,这使其成为供应商固件期望如何被驱动的最佳来源:固件 blob 路径和加载顺序、守护进程期望的属性和套接字接口、传感器和调制解调器 HAL 配置。当主线驱动探测成功但固件握手失败时——或者当您需要了解尚未支持的外设期望的握手方式时——缺失的这部分通常在这里有记录。
    • Mobian——Debian 设备仓库。
    • NixOS——mobile-nixos 模式和 nixpkgs 打包。

结果稀疏是正常情况,不是阻碍因素。 移植所需的大部分工作可能从未在此主板上完成过,甚至通常也从未在此 SoC 上完成过。六个来源报告无结果意味着没有人发布过这项工作——这绝不意味着工作应该停止或被退回。当缺乏先例时,答案仍然存在于主要产物中:已经驱动外设的树外供应商驱动(寄存器序列、固件握手、GPIO 和稳压器接线)、原厂 DTB 中该外设自身的节点、供应商 HAL 配置命名用户空间期望的内容,以及作为结构模板的同类设备最接近的主线驱动。阅读这些内容,推导设计,并编写缺失的部分——一个 DT 节点、一个 quirck、一个小的驱动。编写这部分工作是主线化适配过程的正常最终状态,是需要证明的例外;阶段 0 的备份和每次刷写一个变量的规则正是为了确保一次错误的推导只需付出一次启动和一次日志行的代价,与任何其他反证相同。

扫荡无结果和扫描无结果是不同的发现。 对来源的扫荡无结果意味着没有人发布过这项工作。扫描无结果——一个返回空结果的提取——在相同方法已对已知阳性样本运行并且其覆盖率得到验证之前是无效的:文件计数、段列表、glob 实际匹配了哪些内容。测量到的险些失误:一个 glob 匹配了 27 个固件段中的 9 个,差点记录了一个错误的结论“固件省略了它”;一个库扫描只有在找到一个已知芯片 ID 作为阳性对照后才变得可信。

下游标识符是下游的专有权。 您在供应商源中发现的字符串——寄存器偏移量、芯片 ID、物理地址——是特定于该供应商选择的 SoC 集成方式的,而不是由外设的通用绑定定义的。将这些作为调查起点,但不要期望它们直接在主线中对应。映射它们不是移植工作,而是与维护者讨论时的上下文来源。

3. 实施:一次一个变量,一个完整的启动周期

每次提交或补丁集精确地改变一个变量。不改变的手段:

  • 戴帽子的变量——主线合并窗口以预测的节奏关闭。如果接近窗口截止日期,请验证此更改是否已准备好提交或是否值得等待。
  • 戴帽子的变量——第一次尝试失败得越快,第二次尝试就越快。如果一次闪存写入不成功,请立即放弃它,不要‘再试一次’,并转到下一个可能的写入。

相关技能