从一台乐视 C1 出发移植 Armbian:记录、证据链与心得

3 小时前(已编辑)
6
1

从一台乐视 C1 出发移植 Armbian:记录、证据链与心得

Warning
好的教程应当提供思路、启发读者,而不是直接丢出一个现成的刷机包。即使提供所谓的“懒人包”,也必须明确标注风险,因为即便设备型号相同,不同生产批次之间也可能存在硬件差异。
Note
本文有意不提供任何可下载的系统镜像、恢复内核、分区包、校验值、整盘写入命令或一键脚本。它记录的是如何识别硬件、理解启动链、建立回退路径、构建自己的内核,并用可观察证据逐层验证。

最后整理:2026-08-02
实验对象:乐视 C1 的一个特定 NAND/AML8726-MX/g28ref 样本

在特定样机上启动成功的

在特定样机上启动成功的

1. 为什么我不提供“懒人包”

老嵌入式设备的误导之一,是把商品名当成硬件身份。

两个外壳、型号标签甚至 Android 设置页面完全相同的盒子,内部仍可能存在差异(类似于同型号SSD方案抽奖),如:SoC 或 SoC 小版本不同、一个使用 NAND,另一个使用 eMMC、Bootloader 位于 SPI、NAND、eMMC boot0/boot1 或用户区的不同位置 等等... 而现成刷机包把这些差异隐藏在简单的刷写指令后。

本文采取相反的发布原则:分享调查方法,分享每个结论的证据,分享构建和验证思路,分享失败现象及原因,把可恢复性视为功能。

2. 一次“同型号刷错包”事故带来的直接教训

身边的一次实际经历促成了这次改写:一位朋友使用了作者声称适配同型号设备的 Android 修改包。包内包含 bootloader.PARTITIONboot.PARTITIONsystem.PARTITIONvendor.PARTITION 等完整分区映像。刷写工具能够识别这个包,并不能证明包里的 Bootloader 和分区布局属于朋友手上的硬件修订版。

设备随后失去正常启动能力。到这一步再讨论按键复位、USB burning、boot0/boot1 或短接,只是在一个未知损坏状态上继续猜测。真正应该发生的判断,应在写入 Bootloader 之前完成。

这件事的核心并不是某个作者“粗心”,而是分发方式本身有结构性风险:

正确路线应当反过来:

聊天截图不收入公开教程。一方面是保护朋友隐私;另一方面,事故的价值在于可推广的判断原则,不在于展示当事人的对话。

3. 本文样机的身份是怎样确认的

层次本文样机的观测结果证据来源
SoCAmlogic AML8726-MX / Meson6,ARMv7 双核 Cortex-A9芯片、/proc/cpuinfo、原内核日志
RAM1 GiB,起始物理地址 0x80000000U-Boot DRAM 日志、内核内存布局
原系统Android 4.1.2 / API 16,Linux 3.0.50getpropuname
U-Boot 板型g28ref,machine ID 0x4e27U-Boot banner、环境与启动日志
内置存储约 4 GiB NAND,MTD/UBI/YAFFS2/NFTL;不是 eMMC/proc/mtd、挂载表、NAND 日志
启动存储4 MiB SPI NOR,保存厂商 U-BootSPI 探测与备份结果
有线 PHYSMSC LAN8720 系列,RMII原板资料、PHY ID、Linux MDIO 探测
SD 卡槽Meson SDIO Port B原厂代码、U-Boot mmcinfo、主线内核枚举
样机主板正面。外壳型号之外,PCB 和主要芯片才是可复核信息。

样机主板正面。外壳型号之外,PCB 和主要芯片才是可复核信息。
样机主板背面,可见 PCB 编号以及 UART/JTAG 丝印。

样机主板背面,可见 PCB 编号以及 UART/JTAG 丝印。

意外发现,该设备默认开启无线ADB,又意外发现 adb root 可以直接提权

(美国某不为人知办公室里传来一声轻哼和下周回国)

那还说啥了。果断选择自编译内核运行Armbian。

4. 建立自己的设备身份档案

在考虑编译内核之前,应先做一份只读设备档案。至少包括:

4.1 Android/Linux 层

系统版本和 API 级别
uname 与 /proc/cpuinfo
/proc/mtd 与 /proc/partitions
mount、df 和 vold 配置
网络接口、MAC、PHY 信息
Etc.

4.2 Bootloader 层

完整上电 UART 日志
启动介质探测顺序
bootcmd、recovery、preboot 等环境变量
内核加载地址、入口地址和压缩格式
分区表、坏块和 ECC 日志
Etc.

4.3 备份层

至少应该清楚,用什么工具、在什么条件下,能把这台机器恢复回来。

哦对了,没有 OOB/ECC 的 MTD 数据不能被称为完整 NAND 镜像。

5. 为什么选择 SD recovery,而不是 NAND 安装

灵感来自本机备份的 mtd13 U-Boot environment。(设备采用哪条高层启动路径,由 environment 中的 prebootbootcmdnandbootswitch_bootmoderecovery 脚本明确描述)

5.1 从十六进制转储恢复环境变量

备份目录里的 mtd13-ubootenv 提取其中每个两位十六进制数后,可以恢复出恰好 32,768 字节的环境区。

前四字节 31 3c fb 89 是小端保存的 CRC32;其后的内容是以 NUL 分隔的 ASCII name=value 字符串。例如:

十六进制:62 6f 6f 74 63 6d 64 3d 72 75 6e 20 6e 61 6e 64 62 6f 6f 74 00
ASCII:   bootcmd=run nandboot\0

5.2 与启动选择直接相关的原始变量

以下内容是从环境区逐字节解码得到的 ASCI:

bootcmd=run nandboot

loadaddr=0x82000000
machid=4e27
console=ttyS2,115200n8

nandargs=run cvbscheck;nand read aml_logo 0x84100000 0 400000;setenv bootargs root=/dev/cardblksd2 rw rootfstype=ext3 rootwait init=/init mac=${ethaddr} logo=osd1,0x84100000,${outputtemp},full androidboot.resolution=${outputmode} nohlt vmalloc=256m mem=1024m a9_clk_max=1512000000

nandboot=echo Booting from nand ...;run nandargs;nand read boot ${loadaddr} 0 800000; if bootm; then echo ok;fi; run recovery

recovery=echo enter recovery;run nandargs;if mmcinfo; then if fatload mmc 0 ${loadaddr} uImage_recovery; then bootm;fi;fi; nand read recovery ${loadaddr} 0 600000; bootm

preboot=if itest ${letv_factory_reset} == 1;then run letv_env_reset;fi;get_rebootmode; clear_rebootmode; echo reboot_mode=${reboot_mode}; if test ${reboot_mode} = usb_burning; then tiny_usbtool 20000; fi; run switch_bootmode

switch_bootmode=if test ${reboot_mode} = factory_reset; then run recovery;fi

reboot_mode=normal

为了看清控制流,我们格式化一下:

bootcmd:
    run nandboot

nandboot:
    echo "Booting from nand ..."
    run nandargs
    nand read boot ${loadaddr} 0 800000
    if bootm; then
        echo ok
    fi
    run recovery

recovery:
    echo "enter recovery"
    run nandargs
    if mmcinfo; then
        if fatload mmc 0 ${loadaddr} uImage_recovery; then
            bootm
        fi
    fi
    nand read recovery ${loadaddr} 0 600000
    bootm

preboot:
    if ${letv_factory_reset} == 1; then
        run letv_env_reset
    fi
    get_rebootmode
    clear_rebootmode
    echo reboot_mode=${reboot_mode}
    if ${reboot_mode} == usb_burning; then
        tiny_usbtool 20000
    fi
    run switch_bootmode

switch_bootmode:
    if ${reboot_mode} == factory_reset; then
        run recovery
    fi

5.3 我们能从这些变量得出什么

TL;DR:正常启动首先尝试 NAND bootfactory_reset reboot mode 进入 recovery;recovery 首先尝试从 mmc 0 上可由 fatload 读取的 FAT 卷加载 uImage_recovery,随后才回退到 NAND recovery。这条已有路径让我们能够在不改写 U-Boot 环境和 NAND 启动分区的前提下试验 SD 内核。

  • bootcmd=run nandboot:没有特殊 reboot mode 时,默认路径执行 nandboot
  • nandboot:先从名为 boot 的 NAND 分区读取内容并执行 bootm;只有 bootm 返回或失败后,才继续 run recovery
  • nandargs:无论 NAND boot 还是 recovery 都先生成厂商 Android 的旧式命令行,其中根设备是 /dev/cardblksd2、文件系统是 ext3、入口是 /init。这也解释了为什么实验内核不能照单全收厂商 bootargs,而必须根据实际 SD 枚举结果建立自己的命令行。
  • prebootswitch_bootmode:U-Boot 读取 reboot mode,随即清除它;当值为 factory_reset 时执行 recovery。这与 UART 中看到的 reboot_mode=factory_resetenter recovery 一致。
  • recovery:先用 mmcinfo 探测 SD/MMC;成功后,在 mmc 0 上用 fatload 查找 uImage_recovery。文件读取成功就执行 bootm
  • mmcinfofatload 或 SD 内核启动失败并返回,控制流继续从 NAND 的 recovery 分区读取镜像并执行。这就是 SD 优先、NAND recovery 兜底的直接证据。
  • loadaddr=0x82000000 是 U-Boot 暂存整个 legacy uImage 的地址;uImage 头中记录的内核最终加载/入口地址是另一层概念,二者不应混为一谈。

这意味着可以把所有实验状态放在可拔除介质上:

这条路线的价值不只是方便,还降低了很多操作原厂NAND风险。SD卡一拔,Android、ADB 和原 SSH 还活着。

本机 NAND 已观察到 factory bad block 和局部 ECC 异常。主线内核的 NAND 控制器、OOB、坏块处理及内置根文件系统也没有完成验证。因此“刷进 NAND”不是完成度更高的版本,而是在可观察性和可恢复性更差的介质上扩大风险。

6. Re0: 从 SD recovery 开始的 Armbian 构建

叽里咕噜说半天,只证明了一件事:原 U-Boot 留下了一条不改 NAND 的实验入口。它并不会自动给出可启动的 Linux 内核和用户空间。下一步要把四类材料组合起来:

6.1 初尝试

第一版步子迈小一点,目标:

  • ARMv7/armhf 内核和最小 Bookworm 用户空间;
  • AO UART console;
  • SD 卡控制器和 ext4 根文件系统;
  • 板载 Ethernet 与 SSH;
  • 不启用 NAND 写入;
  • 暂不承诺 HDMI、Mali、VPU、红外、Wi-Fi 和硬件视频解码。

[!Note]

上面 mermaid 里那堆东西 —— board config、最小 DTS、内核配置+补丁、rootfs 定制、构建内核、rootfs 与实验介质布局 —— 制定好目标只有,以现在的Agent能力,你可以完全丢给它来执行!(本文的构建就是这么干的)

如果不擅长制定目标的话,欸,可以试试Harness哦!好的 Agent 会把你一句模糊的需求追问成可执行的目标。

= 问题出现 =

第一次触发 recovery 后,视频输出黑屏,Android 网络服务消失,预期的 Armbian SSH 也没有出现。此时“编译成功”和“写卡校验通过”已经不足以继续判断:问题可能在 U-Boot 读卡、内核封装、DTB、console、SD 驱动或根文件系统中的任何一层。

6.2. UART:不要让黑屏替你下结论

上面一连串问题,都不能单独证明内核没有运行。对于旧式 Bootloader 和新内核的组合,UART 是最直接的事实来源。

本文样机四个 UART 焊盘按照片从上到下如图:

UART 焊盘特写。此排列只对照片中的 PCB 修订版成立。

UART 焊盘特写。此排列只对照片中的 PCB 修订版成立。

但我发现,这个几把 居然需要GND -> GND, RX -> RX才能读出串口信息,而非TX -> RX,原理未知,总之十分滑稽。

7. 排错的关键结论

从 U-Boot 到 rootfs 的排错基本由 AI 代劳:目标是我定的,日志是它读的,修复是它提的,我负责验收。过程几千字,读者没必要跟着重走一遍,这里只留结论和证据。

内核交接:旧 Bootloader 不传 DTB。 厂商 U-Boot 2011.03 走到 Starting kernel ... 后不再有任何输出,而外层 uImage 的 CRC、LZMA、加载/入口地址全部正常——问题出在跳转之后:旧 U-Boot 只传 machine ID(0x4e27)和 ATAG,不传现代 DTB;raw Image 尾部拼 DTB 也没用,CONFIG_ARM_APPENDED_DTB 的扫描只在 zImage 自解压器里执行。改成 zImage + appended DTB 并临时启用早期调试输出后,invalid dtb and unrecognized/unsupported machine ID 实锤,正常日志恢复。

MMC:一行日志、两个问题。 Waiting for root device /dev/mmcblk0p2...no support for card's volts 同时出现,但实际卡槽是 SDIO Port B,主线枚举为 mmc1——根参数指错了;Meson-MX SDIO 驱动拿到的 OCR 电压范围为空,需要只对空值生效的 3.2–3.4 V 回退。设备树遵循同一条纪律:内存 0x80000000、AO UART、mmc1、SMSC LAN8720(RMII)每个关键值都来自实机日志或原厂代码,不猜;没验证的外设(HDMI、Mali、VPU、红外、Wi-Fi、NAND)一律保持禁用。

驱动与构建的坑。 SMSC PHY 驱动做成 module 时,PHY 在它加载前就被 Generic PHY 绑走,链路周期性 Down/Up——启动早期不可替代的硬件要 built-in。构建侧:WSL 的代理变量会流进容器,导致源码拉取失败;board family 继承错板型会去下载 Odroid C1 的 U-Boot(名字相同的老朋友又出现了,可恶啊幻觉);固定补丁不匹配新内核版本时要重做而不是强套;配置文件写了不等于二进制采用了,一切以从最终镜像反查 .config 为准。

日志误判几件事。 Starting kernel ... 不是成功或死机,是U-Boot留下的最后一句话;相同消息出现三次,可能只是多个 console 在输出同一条日志。

8. 分阶段验证

移植应按依赖关系设置验收门槛:

阶段目标成功证据失败时停止在哪里
0原系统与恢复入口备份、UART、拔卡回 Android不构建、不写介质
1U-Boot 读取 SD 文件文件大小、CRC、解压均正常检查介质与外层格式
2内核取得 DTB出现正常 Linux early boot检查 zImage/DTB/ATAG
3SD 控制器与根分区mmc 枚举、ext4 挂载检查 DTS、OCR、root 参数
4userspaceinit、systemd、登录 shell检查 rootfs 与内置驱动
5Ethernet/SSH正确 PHY 驱动、真实 SSH 会话检查 PHY、时钟、networkd
6稳定性多次冷启动、长时间 I/O/网络不扩展 HDMI/NAND 功能

每个阶段只修日志中最先出现、证据最明确的问题。把 MMC、DTB、网络和显示的猜测同时塞进一个"大修版"内核,会失去因果关系。

9. 运行后的只读验收思路

启动后应收集而不是立刻sudo apt install fastfetch并screenshot,你这辈子也是有了:

内核版本、架构、设备树 model
最终 cmdline
根分区和 /boot 的真实来源
MMC 拓扑与错误计数
MAC、PHY ID、绑定驱动和 carrier 计数
systemd failed units
完整 dmesg
多次冷启动结果
长时间网络与存储 I/O 结果

特别注意区分三个层次:

  • 构建验证:配置和字节结构符合预期;
  • 启动验证:这台样机能进入系统;
  • 稳定性验证:长时间和重复启动没有退化。

只有前两项时,不能写"完美适配";只有某个功能偶尔工作时,也不能写"已支持"。

10. 不要在晚上救砖

拆机了 格机了

统一原则:停止反复刷写和格式化,保存好串口日志。

一个可信的回退方案应在实验前回答:

  • 失败后如何让 Bootloader 不再选择实验介质?
  • 是否只需拔卡即可回到原系统?
  • recovery mode 是否会被清理?
  • Android、ADB、SSH、UART 中至少哪两条仍可用?
  • 如果 SD 上的新内核失败,如何恢复上一个已知可启动版本?
  • 如果 Bootloader 本身被写坏,是否有已验证的 USB/SPI/NAND 恢复工具?

本文样机选择 SD recovery,正是因为前四项能在不写 NAND 的情况下得到肯定答案。没有验证过的"短接救砖""按住 reset 就行"不应作为刷写前的风险担保。

11. 我能证明什么

已经由早期调试构建在这台样机上证明

  • 厂商 SPI U-Boot 能通过 recovery 路径读取 SD 上的自构建内核;
  • zImage + appended DTB 的交接方式可行;
  • Linux 6.12.98 能使用 1 GiB RAM;
  • SDIO Port B、FAT 启动分区和 ext4 根分区可用;
  • UART、systemd、networkd、有线 Ethernet 和 SSH 能进入工作状态。

已完成构建检查、仍需运行复验

  • SMSC PHY 内置后的正式内核;
  • Link Down/Up 是否完全消失;
  • 去掉多重 early console 后的正式串口行为;
  • 与同次构建模块配套的长期稳定性。

未确认、不能从本文推导

  • 其他 PCB 修订版的兼容性;
  • HDMI 图形输出和桌面环境;
  • Mali-400 3D;
  • Amlogic VPU 视频硬解;
  • 红外遥控;
  • USB Wi-Fi;
  • 所有 USB 口与电源控制;
  • 主线 NAND 读写和 Armbian 内置安装。

12. 碎碎念

刷机不是逆天行道,是跨层契约。 UART 是唯一按真实时间顺序说话的证据。 失败记录保留顺序才有价值。

13. 留给下一位探索者

  • 我确认了 PCB 板号与修订号、SoC、RAM 和内置存储类型,而不只有外壳型号。
  • 我有完整上电 UART 日志,知道 Bootloader 位于哪里,正常与 recovery 分支各做什么。
  • 我有带大小与哈希清单的关键分区备份,并知道其 OOB/ECC 边界。
  • 我能在不写内置存储的情况下启动实验系统,失败时拔除介质即可回到原系统。
  • 我的设备树每个关键节点都有证据来源。
  • 我能解释外层镜像格式、入口地址和 DTB 传递方式。
  • 我检查了最终二进制,而不只检查构建配置;启动验证与稳定性验证分开记录。
  • 我不会把当前样机的产物标为"同型号通刷"。

任何一项为"否",都应该回到只读调查阶段,清楚你在干什么,而不是寻找更强的刷写工具/刷机包。

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...