从一台乐视 C1 出发移植 Armbian:记录、证据链与心得
最后整理:2026-08-02
实验对象:乐视 C1 的一个特定 NAND/AML8726-MX/g28ref 样本

在特定样机上启动成功的
1. 为什么我不提供“懒人包”
老嵌入式设备的误导之一,是把商品名当成硬件身份。
两个外壳、型号标签甚至 Android 设置页面完全相同的盒子,内部仍可能存在差异(类似于同型号SSD方案抽奖),如:SoC 或 SoC 小版本不同、一个使用 NAND,另一个使用 eMMC、Bootloader 位于 SPI、NAND、eMMC boot0/boot1 或用户区的不同位置 等等... 而现成刷机包把这些差异隐藏在简单的刷写指令后。
本文采取相反的发布原则:分享调查方法,分享每个结论的证据,分享构建和验证思路,分享失败现象及原因,把可恢复性视为功能。
2. 一次“同型号刷错包”事故带来的直接教训
身边的一次实际经历促成了这次改写:一位朋友使用了作者声称适配同型号设备的 Android 修改包。包内包含 bootloader.PARTITION、boot.PARTITION、system.PARTITION、vendor.PARTITION 等完整分区映像。刷写工具能够识别这个包,并不能证明包里的 Bootloader 和分区布局属于朋友手上的硬件修订版。
设备随后失去正常启动能力。到这一步再讨论按键复位、USB burning、boot0/boot1 或短接,只是在一个未知损坏状态上继续猜测。真正应该发生的判断,应在写入 Bootloader 之前完成。
这件事的核心并不是某个作者“粗心”,而是分发方式本身有结构性风险:
正确路线应当反过来:
聊天截图不收入公开教程。一方面是保护朋友隐私;另一方面,事故的价值在于可推广的判断原则,不在于展示当事人的对话。
3. 本文样机的身份是怎样确认的
| 层次 | 本文样机的观测结果 | 证据来源 |
|---|---|---|
| SoC | Amlogic AML8726-MX / Meson6,ARMv7 双核 Cortex-A9 | 芯片、/proc/cpuinfo、原内核日志 |
| RAM | 1 GiB,起始物理地址 0x80000000 | U-Boot DRAM 日志、内核内存布局 |
| 原系统 | Android 4.1.2 / API 16,Linux 3.0.50 | getprop、uname |
| U-Boot 板型 | g28ref,machine ID 0x4e27 | U-Boot banner、环境与启动日志 |
| 内置存储 | 约 4 GiB NAND,MTD/UBI/YAFFS2/NFTL;不是 eMMC | /proc/mtd、挂载表、NAND 日志 |
| 启动存储 | 4 MiB SPI NOR,保存厂商 U-Boot | SPI 探测与备份结果 |
| 有线 PHY | SMSC LAN8720 系列,RMII | 原板资料、PHY ID、Linux MDIO 探测 |
| SD 卡槽 | Meson SDIO Port B | 原厂代码、U-Boot mmcinfo、主线内核枚举 |

样机主板正面。外壳型号之外,PCB 和主要芯片才是可复核信息。

样机主板背面,可见 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 中的 preboot、bootcmd、nandboot、switch_bootmode 和 recovery 脚本明确描述)
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\05.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
fi5.3 我们能从这些变量得出什么
TL;DR:正常启动首先尝试 NAND
boot;factory_resetreboot mode 进入 recovery;recovery 首先尝试从mmc 0上可由fatload读取的 FAT 卷加载uImage_recovery,随后才回退到 NANDrecovery。这条已有路径让我们能够在不改写 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 枚举结果建立自己的命令行。preboot与switch_bootmode:U-Boot 读取 reboot mode,随即清除它;当值为factory_reset时执行recovery。这与 UART 中看到的reboot_mode=factory_reset、enter recovery一致。recovery:先用mmcinfo探测 SD/MMC;成功后,在mmc 0上用fatload查找uImage_recovery。文件读取成功就执行bootm。- 若
mmcinfo、fatload或 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 修订版成立。
但我发现,这个几把 居然需要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 | 不构建、不写介质 |
| 1 | U-Boot 读取 SD 文件 | 文件大小、CRC、解压均正常 | 检查介质与外层格式 |
| 2 | 内核取得 DTB | 出现正常 Linux early boot | 检查 zImage/DTB/ATAG |
| 3 | SD 控制器与根分区 | mmc 枚举、ext4 挂载 | 检查 DTS、OCR、root 参数 |
| 4 | userspace | init、systemd、登录 shell | 检查 rootfs 与内置驱动 |
| 5 | Ethernet/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 传递方式。
- 我检查了最终二进制,而不只检查构建配置;启动验证与稳定性验证分开记录。
- 我不会把当前样机的产物标为"同型号通刷"。
任何一项为"否",都应该回到只读调查阶段,清楚你在干什么,而不是寻找更强的刷写工具/刷机包。