
把 openFyde 移植到 LubanCat-2:一次 RK3568 的完整踩坑记录
自用记录。目标是半年后的自己看到这篇能直接重建整套东西,而不是重新推一遍。 所有数字、commit hash、扇区偏移、寄存器值都是实测值,可以直接拿去对。
0. 这是什么
把 openFyde(FydeOS 的开源版,本质是 ChromiumOS)移植到野火 LubanCat-2(瑞芯微 RK3568)。
上游官方只支持 RK3588。RK3568 是从零开始,没有任何现成的板级支持。整个工作分两段:7 月打通“能开机进桌面”,8 月打通“镜像能独立启动 + 全部固化进构建系统”。
基线(记在 patches/BASELINE):
openFyde 版本 R132-16093.91.0
Chrome M132
内核 6.1.75 (openFyde/kernel-rockchip_6, 分支 rk6.1-openfyde)
内核上游基线 705e94619
板上固件版本(全部实测,排查时经常要对):
SPL 2017.09-g606f72bd97a-240527 #lxh (May 30 2024)
DDR 2d653b3476 typ 24/01/20-15:04:19, fwver v1.21
BL31 v2.3-948-g0a207bf3c, fwver v1.46
OP-TEE 3.13.0-1018-g3864e29ae, fwver v2.16
1. 现在能干什么,不能干什么
能:
- 完整 ChromeOS 桌面,从 SD 卡独立启动(不借 eMMC 上厂商的任何东西)
- GPU 硬件加速:Panfrost + Mesa 25,Chrome 走 ANGLE → GLES
- 串口 root shell(UART2 @ 1500000 baud,不是 115200)
- 百兆有线网
不能:
- Android 应用 —— ARC 需要 Google 授权
- Crostini / Linux 容器 ——
vm_concierge收 TRAP 崩溃、crosdns退出码 74,无限重启。需要 KVM,这条没通 - 在线登录 —— 没有 Google/FydeOS API key。本地账号可以建、可以登
- NPU —— 被主动禁用(见第 6 节)
- 千兆网 —— 直连链路 1Gbps 下 93% 丢包,必须降到 100Mbps 全双工
2. 先建立地图:启动是分层的
调试嵌入式启动问题,第一步永远是判断“报错来自哪一层”。
BootROM 芯片里烧死的代码,改不了
↓
SPL 跑在 SoC 内部 SRAM 里(几百 KB),初始化 DDR、把完整 U-Boot 读进内存
↓
ATF (BL31) ARM Trusted Firmware,特权层
OP-TEE (BL32) 安全世界
↓
U-Boot proper 跑在 DDR 里,扫描分区、执行 boot.scr、加载内核
↓
Linux 内核
↓
init (upstart) ChromeOS 用 upstart,不是 systemd
↓
Chrome / UI
各层在串口里的特征输出:
| 层 | 特征 |
|---|---|
| SPL | DDR 2d653b3476 typ ...、the read training result:、U-Boot SPL 2017.09-... |
| ATF | NOTICE: BL31: v2.3... |
| OP-TEE | I/TC: OP-TEE version: 3.13.0-... |
| U-Boot | U-Boot next-dev-...、Hit key to stop autoboot |
| 内核 | [ 0.000000] Booting Linux on physical CPU |
| init | init: xxx main process ... respawning |
还有一层是 Portage overlay,决定“哪些包进镜像”:
chromiumos-overlay 上游 ChromiumOS
↓
overlay-inaugural-openfyde openFyde 通用层
↓
foundation-rk3568 芯片层(内核 dts、rockchip-mpp)
↓
overlay-lubancat2-openfyde 板级层(我们的东西)
越靠下优先级越高,但跨 overlay 同名包是比版本号,不是比层级 —— 这个坑后面会踩到。
第一部分:七月 —— 让它开机
3. 7 个 Bug:本质是“RK3588 配置串味”
foundation-rk3568 这一层是从 RK3588 抄过来的,内核 kconfig 里全是 RK3588 的开关。前 5 个 bug 都是这么来的。
| # | Bug | 类型 | 现象 | 根因 | 解法 |
|---|---|---|---|---|---|
| 1 | CRU 时钟驱动缺失 | kconfig | 卡 Waiting for root device |
CLK_RK3588=y 但无 CLK_RK3568 |
CONFIG_CLK_RK3568=y + CPU_RK3568=y |
| 2 | GPU CSF 错配 | kconfig | GPU 寄存器映射失败 -5 | CSF 是 RK3588 Valhall 的特性 | 关 MALI_CSF_SUPPORT |
| 3 | GPU devfreq 死锁 | kconfig | GPU probe 后硬死 | devfreq 触发 DDR DVFS 经 ATF 死锁 | 关 MALI_BIFROST_DEVFREQ |
| 4 | clk_disable_unused 锁死 | bootargs | 启动后期 SoC 死 | 内核关掉了无人认领的关键时钟 | clk_ignore_unused |
| 5 | DTB chosen/fiq 劫持 | dts | VFS panic 重启循环 | vendor dtsi 硬编码 eMMC root | /delete-node/ chosen; fiq-debugger |
| 6 | Mali 闭源死局 | 驱动架构 | Chrome GPU 起不来 | 内核是孤儿版 g21p0 DDK | 改用 Panfrost + Mesa |
| 7 | vfat 内核 bug | 内核 bug | 89 秒必崩重启循环 | fat_fill_super NULL deref |
关 CONFIG_VFAT_FS |
Bug 1:CRU 时钟驱动缺失
现象:内核卡在 Waiting for root device PARTUUID=...,rootfs 永远挂不上。
诊断:串口日志里所有外设都是 EPROBE_DEFER (-517) —— mmc、i2c、serial、usb、gpu 全部“延迟探测”。顺着 defer 链往上追:
rockchip-pm-domain: failed to get clk at index 0: -517
PM 域控制器拿不到时钟 → CRU(主时钟控制器)没绑定 → 谁要时钟谁 defer。
根因:kconfig 抄自 RK3588,有 CONFIG_CLK_RK3588=y 但没有 CONFIG_CLK_RK3568。clk-rk3568.c 根本没编进内核。
隐藏陷阱(这个很值得记):加了 CONFIG_CLK_RK3568=y 还不够。olddefconfig 会静默丢弃它 —— 因为 Kconfig 里 CLK_RK3568 depends on CPU_RK3568 || COMPILE_TEST,而配置里是 CPU_RK3588=y + # CONFIG_CPU_RK3568 is not set。
两个都要开:
CONFIG_CLK_RK3568=y
CONFIG_CPU_RK3568=y
教训:kconfig 选项被静默丢弃是常态。改完一定要回头 grep 生成的 .config 确认它真的在。
Bug 2:GPU CSF 错配
现象:Register map failed error = -5、Insufficient register space。
根因:CONFIG_MALI_CSF_SUPPORT=y。CSF(Command Stream Frontend)是 Valhall 架构的特性(RK3588 的 Mali-G610)。RK3568 的 Mali-G52 是 Bifrost 架构,用 JM(Job Manager)。
CSF 驱动会把 DT 里的 reg_size 覆盖成 CSF doorbell 的大小,在 G52 较小的寄存器区上越界,request_mem_region 失败。
Bug 3:GPU devfreq 死锁
现象:关掉 CSF 后 GPU probe 成功了,但启动约 0.74 秒硬死 —— 串口冻结,冷启动必死、热重启正常。
诊断关键:冷热差异 = 经典的 regulator/DVFS 问题。冷启动时 PMIC 电压从默认值开始,GPU/NPU 的 OPP 电压设置经 ATF 触发 DDR 频率切换,然后死锁。
根因:CONFIG_MALI_BIFROST_DEVFREQ=y 让 GPU 参与 devfreq,经 rockchip-dmc 触发 DDR DVFS。
这个“冷热行为不同 → 怀疑电源/时序”的判断方式,8 月排查 NPU panic 时又用上了一次。
Bug 4:clk_disable_unused 锁死 SoC
现象:前面都修好后,启动后期 SoC 死,而且假象很多 —— 死点看着在 GPU/NPU/蓝牙附近,实际都无关。
诊断手段(很通用):bootargs 加 initcall_debug ignore_loglevel,逐个 initcall 追踪,看到:
calling clk_disable_unused+0x0/0xe4 @ 1 ← 进去了就没出来
clk_disable_unused() 是 late_initcall,会关掉所有“没有驱动认领”的时钟。RK3568 上某个关键但无认领的时钟被关掉 → SoC 锁死。
解法:bootargs 加 clk_ignore_unused。这是 boot.scr 层的改动,不用重编内核。
Bug 5:DTB chosen/fiq 劫持
现象:换上正确的 vendor dtb 之后,VFS panic 重启循环。
根因:vendor 的 rk3568-lubancat-2.dtsi 里硬编码了:
chosen {
bootargs = "...console=ttyFIQ0 root=PARTUUID=614e0000-0000 rw rootwait";
};
614e0000 是厂商 eMMC 系统的 root PARTUUID。我们从 SD 卡启动,PARTUUID 完全不同。这个 chosen 节点劫持了内核 cmdline,我们从 boot.scr 传的参数根本没生效。
解法:dts 里删掉:
/ { /delete-node/ chosen; /delete-node/ fiq-debugger; };
副作用(8 月才发现):删掉
fiq-debugger之后 UART2 变成无人认领,/dev/ttyS2从未注册,内核控制台接管不了 earlycon,串口彻底没了。所以还要补一句&uart2 { status = "okay"; };—— vendor dtsi 里 uart2 是 disabled 的,因为它本来被 fiq-debugger 占着。
Bug 7:vfat 内核 bug
现象:桌面起来后约 89 秒必崩,重启循环:
Internal error: Oops: 0000000096000005
pc : fat_fill_super+0x10c/0x1030 [fat]
Comm: mount
Kernel panic - not syncing: Oops: Fatal exception
诊断过程(排除法教科书):
- 关掉 eMMC(sdhci disabled)→ 仍崩 → 不是 eMMC
- 系统里唯一的 vfat 是 SD 卡的 EFI 分区(p12),但
fsck完全正常 - 崩溃前 1.5 秒出现 loop6(14336 扇区 ≈ 7MB)→ 某个组件在用 loop 挂载一个 vfat 镜像
fat_fill_super+0x10c是 NULL deref(偏移 0x1c)→ fat 驱动自己的 bug(6.1.75-rockchip)
解法:桌面根本不需要 vfat(SD 的 EFI 分区是给 U-Boot 读的,内核不用挂),从内核里拔掉:
# CONFIG_FAT_FS is not set
# CONFIG_VFAT_FS is not set
临时验证时是删
vfat.ko.gz/fat.ko.gz模块(因为原本是=m)。
4. Bug 6 单独成章:GPU 的闭源死局与开源破局
这是整个移植最硬的一关。
闭源死局
板子的 GPU 是 Mali-G52。要用闭源栈,需要内核态 DDK(kbase)和用户态 libmali 版本严格匹配。
问题在于:厂商内核带的是一个孤儿版 g21p0 DDK,而世界上不存在与之匹配的 G52 libmali 二进制。ARM 只把 libmali 发给 SoC 厂商,Rockchip 公开的 libmali 仓库里没有这个组合。
这不是配置问题,是这个东西不存在。 试过让内核谎报 DDK 版本(openfyde: report DDK version g24p0-00eac0 to match libmali)、试过放宽 UK 握手(openfyde: permissive mali UK handshake),都是死路 —— 握手能糊弄过去,但 ABI 对不上,EGL 初始化照样失败。
开源破局
改用 Panfrost(内核态)+ Mesa(用户态)。这是一条完全独立的实现,不依赖任何闭源库。
make.conf:
USE="${USE} -mali panfrost"
-mali 是必须的 —— 上游 overlay-inaugural 会继承下来一个 mali 的 USE flag,不显式关掉会打架。
怎么证明 GPU 真的在加速
不用进桌面、不用开 chrome://gpu。 板子上跑:
ls -l /proc/*/fd 2>/dev/null | grep -o 'renderD[0-9]*' | sort | uniq -c
for n in 128 129 130; do echo -n "renderD$n -> "; basename $(readlink -f /sys/class/drm/renderD$n/device/driver); done
实测:
4 renderD129 ← 4 个 fd 正持有
renderD128 -> rockchip-drm (显示控制器)
renderD129 -> panfrost ★ GPU
renderD130 -> RKNPU
判据为什么成立:libgallium-25.0.0.so 里 panfrost(硬件)和 swrast(软件)都在,所以“库被加载了”区分不了两者。但只有硬件加速会去打开 DRM render node,软件渲染不会。renderD129 被实际占用 = Panfrost 在干活。
DRM minor 编号从内核 log 里对:[drm] Initialized panfrost 1.2.0 ... on minor 1 → minor 1 → renderD129。
Chrome 沙箱
Panfrost 是运行时 dlopen 加载 libgallium-25.0.0.so 的,而 Chrome 的 GPU minijail 沙箱会拦住这个动作 —— GPU 进程 Permission denied 直接死,Chrome 报 Software compositing fallback is unavailable。
bring-up 阶段先加 --disable-gpu-sandbox。这是临时手段,正路是修沙箱的路径白名单。
第二部分:八月 —— 让镜像独立可启动
7 月的成果有个前提:用 eMMC 上厂商的 U-Boot 去加载 SD 卡上的内核和 rootfs。镜像本身“能不能自己启动”从没验证过。8 月这一段就是解决这个,顺便把所有手工修补固化进构建系统。
5. SPL 堆耗尽(主戏,卡了两天)
5.1 现象
Trying to boot from MMC2
No misc partition
sys malloc pool space exhausted
alloc_read_gpt_entries: ERROR: Can't allocate lX bytes for GPT Entries
GPT: Failed to allocate memory for PTE
part_get_info_efi: *** ERROR: Invalid GPT ***
part_get_info_efi: *** Using Backup GPT ***
spl: partition error
Trying fit image at 0x4000 sector
## Checking atf-1 ... OK
## Checking uboot ... sha256(...) + Error: inflateInit2() returned -4
uboot: decompress error, ret=-1
inflateInit2() 返回 -4 = Z_MEM_ERROR,zlib 申请不到解压窗口。注意 sha256 校验是 OK 的 —— 镜像内容无损,ATF 和 FDT 也都加载成功,唯独 U-Boot 需要解压所以挂了。
5.2 三条弯路(都只是把死亡点挪位置)
弯路一:把 FIT 里的 U-Boot 存成未压缩。
拆开 FIT,用 compression="none" 重新打包。结果 inflateInit2 那个错确实没了,但死点后移到加载 loadables:
## Checking uboot 0x00a00000 ... OK
## Checking fdt ... OK
sys malloc pool space exhausted
## Checking atf-2 0xfdcc1000 ... OK ← 还能继续,但最终还是挂
弯路二:抹掉 SD 卡的 idblock(扇区 0x40 的 RKNS 魔数),想让 BootROM 转去用 eMMC 的 SPL。
结果:BootROM 确实转向 eMMC 了,但没用。SPL 被载入之后,会按自己编译时写死的设备顺序找完整 U-Boot,而 MMC2(SD)排第一,于是照样跑去读 SD。
BootROM 从哪里载入 SPL、和 SPL 去哪里找 U-Boot,是两件完全独立的事。
弯路三:连 SD 上的 FIT 一起抹掉(0x4000 主副本 + MB 12 / MB 14 的备份副本,都在 RWFW 分区里,不伤数据)。
结果 —— 这条是决定性证据:
Trying fit image at 0x4000 sector
Not fit magic ← 抹除生效
Trying to boot from MMC1 ← 成功退到 eMMC
...GPT 报错 × 8 轮...
## Checking uboot ... sha256(851953c210...) + OK ← sha 不同,确认是 eMMC 自己的 FIT
sys malloc pool space exhausted
## Checking atf-2 ... [复位]
它真的用上了 eMMC 的厂商 U-Boot,然后死在完全相同的地方。
5.3 根因
看弯路三那段:SPL 转到 MMC1 之后,又打了 8 轮 GPT 报错。对上 U-Boot 源码:
- SPL 用的是
malloc_simple—— 只分配、从不释放 - ChromeOS 标准 GPT 是 128 项 × 128 字节 = 16KB,一次申请就超过整个堆
- SPL 会逐个 MMC 设备枚举分区表,主表失败试备份表,每个设备好几轮
结论:只要 SD 卡插着、卡上带着那张 16KB 的 GPT,SPL 的堆就必然被啃穿,之后从哪个设备加载 U-Boot 都会失败。
厂商系统为什么没事?eMMC 上是 512 字节的 MBR,解析代价几乎为零。
我一度以为 eMMC 上有 Rockchip 私有的
parameter分区(log 里No misc partition就是 SPL 在找它),实测前 32MB 里一个PARM都没有。这条假设是错的,别再往这个方向查。
5.4 解法:GPT 分区项 128 → 32
16KB → 4KB,SPL 就吃得下了。
我们只定义 12 个分区,全在前 32 个槽位,所以分区项本身、起止扇区、PARTUUID 一律不动,只改三个字段:
| 偏移 | 字段 | 改法 |
|---|---|---|
| 80 | NumberOfPartitionEntries |
128 → 32 |
| 88 | 分区项数组 CRC32 | 按 32×128 = 4096 字节重算 |
| 16 | 头部 CRC32 | 先把该字段清零,再算整个头 |
主表和备份表都要改。核心就这几行:
struct.pack_into("<I", hdr, 80, 32)
struct.pack_into("<I", hdr, 88, zlib.crc32(arr) & 0xffffffff)
struct.pack_into("<I", hdr, 16, 0)
struct.pack_into("<I", hdr, 16, zlib.crc32(bytes(hdr[:hsize])) & 0xffffffff)
效果立竿见影:alloc_read_gpt_entries 和 sys malloc pool space exhausted 全部消失,所有 loadables 加载成功,顺利跳进 U-Boot。
困了两天的问题,靠改一张分区表解决,没有重编 U-Boot。
5.5 一个无法调和的冲突
把缩项做进构建流程(board_make_image_bootable())之后,构建炸了:
WARNING: Primary GPT header is invalid
ERROR: GptValidityCheck() returned 2: GPT_ERROR_INVALID_HEADERS
mount: wrong fs type, bad option, bad superblock on /dev/loop16
原因:vboot 的 cgpt 硬性要求分区项数组总大小恰好 16KB(校验 number_of_entries * size_of_entry 是否等于写死的常量)。
于是:
- SPL 要求 ≤ 4KB
- cgpt 要求 = 16KB
没有哪个值能同时满足。 所以缩项只能作用在最终产物上,等 ChromeOS 的工具全部操作完镜像之后再做 —— 落在 scripts/flash_sd.sh 里。
sgdisk和parted都接受 32 项的 GPT,只有 vboot 的校验更严。
6. 两颗 U-Boot 各会一半,与 NPU panic
SPL 打通后暴露的下一层问题。
| U-Boot | 出处 | 执行 boot.scr | NPU 电源域 |
|---|---|---|---|
厂商 2017.09-231011 #jiawen |
eMMC 扇区 0x4000 | ✗ SCRIPT FAILED(太老,没有 part uuid) |
✓ 正常 |
自编 next-dev-gaeec6f2-250929 #ty |
overlay 打包的 | ✓ 正常 | ✗ panic |
一个重要发现:overlay 里 sys-boot/rk-uboot-lubancat2/files/uboot.img 装的其实是自编那颗。以前从没暴露问题,是因为每次都是 eMMC 的厂商 U-Boot 抢先干活 —— SD 能独立启动之后,才第一次真正用上它。
NPU panic
rockchip-pm-domain fdd90000.power-management:power-controller:
failed to get ack on domain 'npu', target_idle = 0, target_ack = 0, val=0x6
Kernel panic - not syncing: panic_on_set_idle set ...
rockchip_pmu_set_idle_request → rockchip_pd_power → rk_iommu_driver_init
三个要点:
panic()是 vendor 代码里无条件的(drivers/soc/rockchip/pm_domains.c:636),拿不到 ack 就死,没有开关可关- 触发者是 IOMMU,不是 NPU 本身(看调用栈
rk_iommu_driver_init),所以rknpu和rknpu_mmu两个节点都要禁。label 在rk356x.dtsi:1100和:1242 - 曾出现过厂商 4.19 内核也同样 panic,冷断电(拔电源等 30 秒)后恢复 —— 说明存在可被冷断电清除的 PMU 闩锁成分。但我们的内核冷断电后仍 panic,所以不能只靠这个
解法:
&rknpu { status = "disabled"; };
&rknpu_mmu { status = "disabled"; };
openFyde 用不到 NPU(GPU 走 panfrost),为一个用不上的外设去补自编 U-Boot 的板级初始化不划算。
差点走错的方向
一度怀疑是 dtb 变了 —— 手工版 113188 字节 vs 构建版 112696 字节,NPU 恰好在同期开始 panic,看起来因果关系很强。
实测:把两份 dtb 反编译成 dts 做 diff,实质差异只有一处 —— serial@fe660000(UART2)从 disabled 变 okay,正是我们自己要的改动,跟 NPU 毫无关系。
时间上相邻 ≠ 有因果。能 diff 就别推理。
7. 固化进构建系统:portage 的那些坑
这一关代价最大。有三样东西一直是“手工刷上去就好使”但从没进版本控制:boot.scr、串口 shell job、未压缩 U-Boot。结果一张全新卡写上去,三个坑一次性全炸。
7.1 boot.scr
镜像里的 boot-A.scr.uimg 只有 110 字节(上游 chromeos-base/u-boot-scripts 那个两行的通用模板),而能启动的手工版是 612 字节。
不用自己造轮子 —— FydeOS 的 sys-boot/rk3588-uboot-script 早留了口子:用 files/boot-A.cmd 模板 + sed 替换 #ROCKCHIP_DTS# 和 #EXTRA_BOOT_ARGS#。所以只要在 make.conf 里:
EXTRA_BOOT_ARGS="cros_debug cros_secure console=ttyS2,1500000n8 clk_ignore_unused earlycon=uart8250,mmio32,0xfe660000 loglevel=7"
三项的作用:
console=ttyS2,1500000n8—— UART2 是调试口,1.5M 波特率clk_ignore_unused—— Bug 4 的解法,不加会在启动后期锁死earlycon=uart8250,mmio32,0xfe660000—— UART2 的 MMIO 基址,让内核最早期就能出字
但有个包冲突要一并修:上游 chromeos-base/u-boot-scripts 也往同一路径装它那个两行模板,谁后装谁赢。修法是放一个 rk3588-uboot-script-0.0.1-r4.ebuild 覆盖 baseboard 的 -r2,加上:
RDEPEND="chromeos-base/u-boot-scripts"
逼 portage 后装我们。构建日志里验证顺序:
Installing (350 of 767) chromeos-base/u-boot-scripts-0.0.1-r10 ← 上游先
Installing (425 of 767) sys-boot/rk3588-uboot-script-0.0.1-r4 ← 我们后
判据:产物 612 字节 = 对,110 字节 = 被盖了。
7.2 串口 shell
原来是就地改 console-ttyFIQ0.conf,而那文件是 chromeos-base/tty 构建时生成的,改了必然留不住。
tty 包是 USE flag 驱动的(tty_console_ttyS2、tty-baud-1500000),但光开 USE 不够:
- 模板里的
if crossystem "cros_debug?1"在本板会失败(没有可用的 VBoot NVRAM) agetty不带-n -l /bin/bash会要求登录,而 bring-up 阶段没有可用账号
所以自带一份 console-ttyS2.conf:
env TTY_BAUD_RATE=1500000
script
exec agetty -n -l /bin/bash "${TTY_BAUD_RATE}" ttyS2 linux
end script
★ 这个文件必须放在独立包里,不能塞进 chromeos-bsp-lubancat2-openfyde。
因为 bsp inherit chrome-dev-flag,而 chrome-dev-flag.eclass 里的 src_install() 是裸函数定义、没有 EXPORT_FUNCTIONS —— 在 ebuild 里再定义一个同名函数会直接覆盖掉它,六个 Chrome GPU flag 全部消失且毫无报错。
验收时务必回头看一眼 /etc/init/ui.override 里六个 flag 还在不在。
7.3 ttyFIQ0 无限重启
我原以为“stock 的 ttyFIQ0 job 因为设备不存在会自己 stop,无害”。错的:
init: console-ttyFIQ0 main process (4520) terminated with status 1
init: console-ttyFIQ0 main process ended, respawning
那个 job 声明了 respawn,agetty 退出码 1,upstart 每 10 秒重启一次、永远循环,日志直接刷在串口上 —— 而串口是这块板子唯一的调试通道。
解法是装 /etc/init/console-ttyFIQ0.override,内容一行:
manual
不要去改那个 .conf,它是生成的。
7.4 Chrome GPU flags 进不了镜像:四层原因
症状:flags 在 sysroot 的 chrome_dev.conf 里有,镜像里没有。
- 依赖链断裂(根本原因):
overlay-inaugural-openfyde提供的virtual/fydeos-board-spec-0.0.1-r2的 RDEPEND 里没有virtual/openfyde-board-spec,于是chromeos-bsp-lubancat2-openfyde从来没被装进任何镜像。补一个同名同版本的盖掉它(版本必须匹配才盖得住,0.0.1 会输) virtual/u-boot版本输了:我们的是0.0.1,chromiumos-overlay 的是1-r3,portage 跨 overlay 比版本号。改名为u-boot-1-r4- 上游 profile 主动拉黑:
CHROME_REMOVE_FLAGS="${CHROME_REMOVE_FLAGS} --disable-gpu-sandbox",eclass 按名字过滤,精准剔掉这一个而放行其余五个,现象极具迷惑性 - ebuild 改了但版本没变 → portage 复用旧 binpkg
关键认知:真正让 Chrome 吃到 flag 的是 /etc/init/ui.override(env CHROME_COMMAND_FLAG="..."),不是 chrome_dev.conf。而 eclass 是 grep -e "^#" 旧文件 > 新文件 再追加,后装的包整个覆盖而非合并 —— 所以既要 RDEPEND fydeos-default-chromedev-flags 保证我们后装,又要把它那三个 flag 一并抄进我们的 CHROME_DEV_FLAGS。
最终六个:
--use-gl=angle --use-angle=gl --disable-gpu-sandbox
--disable-features=CrostiniUseDlc --disable-buffer-bw-compression --enable-features=QuickUnlockFingerprint
7.5 构建系统会骗你
cros-workon 用本地树还是克隆,取决于本地 HEAD 是否等于 CROS_WORKON_COMMIT:
- 相等 → 直接用
/mnt/host/source/src/third_party/kernel/v6.1-rockchip,改本地文件立即生效 - 不等 → 克隆到
work/,改本地文件完全无效
日志里 >>> Compiling source in <路径> 那行是判断依据,排查时必看。改内核后必须同步 bump CROS_WORKON_COMMIT。
dts 有没有被编译,看日志里 DTC 的行数:339 = 没编我们的,340 = 编了。
Linux 的 dts 是白名单制:光把 .dts 放进目录等于不存在,必须在 arch/arm64/boot/dts/rockchip/Makefile 里登记。而且 make -k 会吞掉 dtc 的失败继续跑,导致 emerge 报 rc=1 但错误要往回翻一千多行。
RK_FUNC_1/2 宏在上游 6.1 被删了(dt-bindings/pinctrl/rockchip.h 只剩 RK_FUNC_GPIO),vendor dtsi 还在用。dtc 报一句没头没尾的 rk3568-lubancat-2.dtsi:1082.10-11 syntax error。补回 RK_FUNC_1..4 即可。
8. 刷卡:Windows 会篡改 GPT
全新卡写好、上板,秒炸:
GUID Partition Table Entry Array CRC is wrong: 0x791f26b4 != 0xc71c0011
part_get_info_efi: *** ERROR: Invalid GPT ***
镜像本身是好的 —— 在 VM 上验证过:头里存的 CRC 0x791f26b4 正好等于 4096 字节数组的实际 CRC,sgdisk 也不报错。
把卡上的 GPT 头逐字段打印:
| 字段 | 卡上的值 | 说明 |
|---|---|---|
n |
32 | 项数没被改 |
ecrc |
0x791f26b4 |
还是我们写的原值 |
entries_lba |
26 | 原本是 2 —— 被挪了 |
| LBA 26 处实际 CRC | 0xc71c0011 |
那里根本不是我们的数组 |
| 头 CRC | 匹配 | 说明头被重新计算过 |
alt_lba |
120881151 | 备份 GPT 被挪到 57.6G 盘尾 |
根因:我们镜像的备份 GPT 在 8.4G 处,而卡是 57.6G —— 备份 GPT 不在盘尾。Windows 认为这是损坏,于是“修复”:重写 GPT 头、把备份挪到盘尾、把分区项数组指针改成 LBA 26,却没把数组数据搬过去,也没重算数组 CRC。
我们的数组从头到尾完好地躺在 LBA 2(CRC 对得上,第一个分区名是 STATE)。被破坏的只有一个指针。
修法:把镜像的前 34 扇区 + 备份区(skip=16564348 count=33)dd 回卡上,几十 KB 的事。
结论:别用 Windows 写卡,走 Linux 侧的 scripts/flash_sd.sh。
诊断要点:读 GPT 头偏移 72 处的
entries_lba,是 2 就正常,是 26 就是被 Windows 动过。
9. 分区布局(备查)
Number Start(sector) End Size Name
11 64 65599 32.0 MiB RWFW ← idblock@0x40, FIT@0x4000 在这里面
6 65600 65600 512 B KERN-C
7 65601 65601 512 B ROOT-C
9 65602 65602 512 B reserved
10 65603 65603 512 B reserved
2 69632 135167 32.0 MiB KERN-A
4 135168 200703 32.0 MiB KERN-B
8 200704 233471 16.0 MiB OEM
12 364544 495615 64.0 MiB EFI-SYSTEM ← boot.scr 在 /boot/boot.scr.uimg
5 495616 499711 2.0 MiB ROOT-B
3 499712 6078463 2.7 GiB ROOT-A
1 6078464 16564332 5.0 GiB STATE
裸扇区:
0x40(64):idblock,魔数RKNS=524b4e530x4000(16384):U-Boot FIT,魔数d00dfeed- MB 12 / MB 14:FIT 的备份副本
都在 RWFW 区内,这个分区 ChromeOS 本身不用,往里写引导器是安全的。
10. 复现步骤
# 1. repo sync 之后先打树外 patch(这些文件不在 overlay 里,sync 会 revert)
git -C chromite apply .../patches/chromite/*.patch
git -C src/scripts apply .../patches/scripts/*.patch
# 2. 内核:apply patches/kernel/*,然后把 CROS_WORKON_COMMIT 指向结果
# 3. 构建
cros build-image --board=lubancat2-openfyde --noenable_rootfs_verification base
# 4. 刷卡(一定从 Linux 侧)
scripts/flash_sd.sh .../chromiumos_base_image.bin /dev/sdX
只改了 rootfs 的话可以只刷 ROOT-A(启动走 EFI 分区里的 boot.scr,不动它就还能起来)。不落盘的流式刷法:
# 编译机侧切出来
dd if=<image> bs=1M skip=244 count=2724 | gzip -1 > /tmp/roota.gz
# PC 只做管道中转
plink ... "cat /tmp/roota.gz" | ssh board "gunzip -c | sudo dd of=/dev/mmcblk1p3 bs=1M conv=fsync"
实测 87 秒写完 2.7G,等效 32.8 MB/s。
11. 方法论:真正学到的
① 调试的第一步是定位层级,不是改代码。 整个过程本质就是把失败点一层层往下推:SPL 堆耗尽 → U-Boot 解压失败 → 内核 NPU panic → init 起来 → 桌面。
② 越早期的阶段,资源约束越离谱。 SPL 的堆只有几 KB,而 ChromeOS 的分区表要 16KB。这不是谁写错了代码 —— 是两套上游各自合理的假设撞在了一起。移植工作的本质就是在中间找可行解。
③ 同一份二进制,喂不同的数据就是活和死的区别。 eMMC 和 SD 上跑的是同一颗 SPL、同一个 git hash,一个活一个死,差别只在盘头那张表。
④ 设计能证伪的实验,而不是连续试修法。 抹掉 SD 的 FIT 逼 SPL 退到 eMMC —— 还是死。这一下同时排除了“设备顺序”和“压缩”两个假设。
⑤ 能 diff 就别推理。 dtb 字节数变了 + NPU 开始 panic,看起来因果很强。反编译一 diff,实质差异只有一个 UART2。
⑥ 非确定性 = 先怀疑物理层。 Bug 3 的“冷启动必死、热重启正常”指向 DVFS/regulator;8 月 NPU panic 靠拔电源等 30 秒清掉 PMU 闩锁。这类症状不要去查逻辑。
⑦ 手工修补就是债,一定会被追讨。 boot.scr、串口 job、未压缩 U-Boot 三样都是“手工刷上去就好使”但没进版本控制,全新卡一写三个坑一起炸。每次手工修好一个东西,当场就问:这进 overlay 了吗?
⑧ 配置项会被静默丢弃。
CONFIG_CLK_RK3568=y 被 olddefconfig 悄悄扔掉,因为依赖没满足。改完一定回头 grep 生成的 .config。
⑨ “应该没事”这四个字要禁用。
“ttyFIQ0 设备不存在会自己停,无害” —— 它带 respawn,每 10 秒刷一次屏。每一个“应该”都该换成一条实测。
12. 遗留问题
-kvm_host:Crostini 三个服务(vm_concierge/crosdns/vhost_user_starter)在疯狂重启,白烧 CPU- 自己编 U-Boot:现在的
idblock.bin是从厂商 eMMC 扒的。正路是用 Rockchip 公开的rkbin+ 开源 U-Boot 源码自己编。这一件事能同时解决三个问题:blob 来源合法性、SPL 堆(直接改大CONFIG_SPL_SYS_MALLOC_F_LEN就不用缩 GPT)、NPU 初始化 - Chrome GPU 沙箱:现在靠
--disable-gpu-sandbox绕过,正路是修路径白名单 - 千兆网:直连链路 1Gbps 下 93% 丢包,锁在 100Mbps 全双工
- STATE 分区扩容:57.7G 的卡只用了 8.4G
- 硬件视频解码:overlay 里有
rockchip-mpp,没确认接进 Chrome - MIPI 摄像头:7 月做过 IMX 传感器移植,没验证
附:关键提交
overlay:
3ae0215 boot.scr 的 bootargs + 串口 console job
d474826 GPT 缩到 32 项
1d32de3 改为构建后执行(cgpt 要求 16KB,冲突不可调和)
7bb2b5c 压制 ttyFIQ0 的 respawn
562caf2 U-Boot 存未压缩
a5625a7 flash_sd.sh
babd702 整理成板级包(patches/ + README)
kernel (rk6.1-openfyde):
c2eb46c add EmbedFire LubanCat-2 (RK3568)
263bf65 restore RK_FUNC_<n> aliases
1b0dce1 disable the NPU