OpenWrt给 RK3506 装一套真能 opkg 的发行版rk-forge 已经开源带你从零把一颗几乎没人理的 RK3506用主线 Linux7.1 主线 U-Boot 一路跑到rk3506 login:——可按序打上去的补丁库、诚实的差距报告、完整 bringup 教程都在这。欢迎观摩喜欢点个⭐仓库地址https://github.com/Awesome-Embedded-Learning-Studio/rk-forge静态网页https://awesome-embedded-learning-studio.github.io/rk-forge/rootfs 那一卷走完buildroot 出的 rootfs 已经能烧进 SPI-NAND、跨冷重启持久、跑进login:连 loader 弱写那场风波都收口了见 rootfs Ch3。但朋友拿到板子要的不是一个 rootfs是一套发行版——能在板子上opkg install现场装包、能开 LuCI 网页配网络、能像一台路由器那样随时加 kmod。这套东西 buildroot 给不了只有 OpenWrt 给得了。这篇就把 OpenWrt 作为--rootfsopenwrtprofile 移植进 rk-forge和 buildroot 并列NAND 和 SD 两条路都在板上跑到了rootOpenWrt:~#。前言buildroot 给得了 rootfs给不了发行版先把 buildroot 和 OpenWrt 的差别讲明白不然后面每个决策都会觉得别扭。buildroot 这套思路是定做一个 rootfs——你在 defconfig 里勾好要哪些包它从源码编一整个根文件系统给你busybox、glibc 运行时、init 脚本、FHS 目录全在里面。想加个工具改 defconfig、重编整个 rootfs、重新打包烧录。它是出厂前定做好板子上没有包管理器加东西的成本是一次完整重编。OpenWrt 是另一套哲学它给你一套能现场生长的发行版。rootfs 里自带opkg包管理器和一套 kmod 内核模块体系板子跑起来之后opkg install luci就能从软件源装包opkg install kmod-usb-storage就能现场加载内核模块不用重编 rootfs、不用重新烧录。这套现场装包的能力是路由器、网关这类产品的命门也是 buildroot 那套定做思路给不了的。但 OpenWrt 代价也不小——它本身就是一整套构建系统自己编 musl 工具链、自己下 linux 内核源码打补丁、自己编 rootfs 和包。咱们 rk-forge 这边已经把主线 U-Boot、rkbin loader、fit-pack.py、rkfw-pack.py 这一整条 RK 专属打包链都板上验过了所以真正要解的就一个接缝问题怎么让 OpenWrt 编出来的东西接进 rk-forge 已经验过的打包链而不是把 rk-forge 的打包推倒重来。这篇就是围绕这条接缝展开的。架构决策让 OpenWrt 自建 kernel rootfsrk-forge 只管打包最关键的决策是这条OpenWrt 的 kernel 和 rootfs谁来编最省事的想法是借 rk-forge 已经编好的 kernel——咱们 boot Ch3 那块板级设备树、那些补丁都板上验过了OpenWrt 只管出个 rootfs 不就完了这条路看上去省事实际上会把 OpenWrt 最值钱的东西废掉kmod。这要从 vermagic 说起。OpenWrt 的每个 kmod 包.ko都带着一段叫vermagic的版本魔法串它就是编这个模块时那个内核.config的指纹——内核打开了哪些选项、用的什么工具链、ABI 是 thumb2 还是 ARM全都哈希进去了。内核加载.ko的时候会拿运行内核自己的 vermagic 去比对模块里的对不上就拒绝加载。内核靠这层校验挡住 ABI 对不上的模块别让乱装的把内核捅穿。而 OpenWrt 的 opkg 软件源里那些 kmod 包vermagic 是跟OpenWrt 自己编的那颗 kernel绑死的。如果 kernel 是 rk-forge 编的、rootfs 是 OpenWrt 编的两边的 vermagic 八竿子打不着板子上opkg install kmod-xxx装下来的模块一个都加载不了kmod 体系当场作废OpenWrt 就退化成一个没有包管理的 busybox——那还移植它干嘛。所以决策很清楚OpenWrt 文档里管它叫选项 Akernel 和 rootfs 都让 OpenWrt 自己编保住 vermagic 天然匹配rk-forge 这边复用它已经验过的主线 U-Boot rkbin loader 纯 Python 打包器负责把 OpenWrt 编出来的东西装进 RK 的 update.img。两边各干自己擅长的事接缝收在最窄处。具体谁负责什么一张表说清环节谁干的说明kernelzImage aes.dtbOpenWrtlinux 7.1 quilt 补丁和 rk-forge 的内核补丁逐字节一致rootfs 树busybox procd kmodOpenWrtmusl 工具链kmod 已经在lib/modules/里U-Bootrk-forge主线板上验过build-uboot.shloaderidbloader / MiniLoaderAllrk-forgerkbinpack-loader.shFITboot.img / uboot.imgrk-forgefit-pack.py板上验过的加载地址update.imgrk-forgerkfw-pack.py还有个意外之喜省了我们不少活OpenWrt 这边 pin 的上游树pins/openwrt→czz8888/rk-3506-openwrt-7.131d15c0已经把 rk-forge 那十六个内核补丁用 quilt 的patches-7.1/series逐字搬过去了——换句话说内核侧那十六个补丁的活儿人家已经干了一半构建时 quilt 会自动 apply咱们不用管。rk-forge 这边只需要补两块 overlay一块告诉 OpenWrt “aes 这块板子长这样”一块补上它 config 里漏的几个开关。overlay 只补两块Device/aes 和 config-7.1这两块 overlay 就放在patches/openwrt/和 linux/uboot 的补丁一个待遇走apply-series.sh --component openwrt用git am落进 OpenWrt 树。注意这里git am的只有 Device 和 config 这点 overlay不包括十六个内核补丁——那些是 quilt 在构建时自己 apply 的别重复打重复打就冲突。第一块0001是给 OpenWrt 注册一块Device/aes_nand。OpenWrt 的 rockchip target 本来有一套自己的rk3506-imgBOOT_FLOW走它自己那套 u-boot idbloader u-boot.itb 的流程——但咱们压根不用 OpenWrt 的 u-boot用的是 rk-forge 的主线 U-Boot。所以这个 Device 把IMAGES和BOOT_FLOW都留空明确跳过 OpenWrt 自己的打包流程define Device/aes_nand $(Device/rk3506) DEVICE_VENDOR : AES DEVICE_MODEL : RK3506B aes (SPI-NAND) DEVICE_DTS : rockchip/rk3506b-aes DEVICE_PACKAGES : kmod-usb2 kmod-usb-storage # rk-forge 用自己的 fit-pack.py rkfw-pack.py 主线 U-Boot 打包 # 不要走 OpenWrt 的 rk3506-img BOOT_FLOW它依赖我们没用的 OpenWrt u-boot IMAGES : BOOT_FLOW : endef TARGET_DEVICES aes_nand注册了这个 Devicemake defconfig才会让咱们在aes-nand.config里选的CONFIG_TARGET_rockchip_rk3506_DEVICE_aes_nandy生效。这块 patch 还有个写补丁时踩的小坑值得提一句手写 unified diff 的时候那个 -55,4 55,17 的 hunk header 行数得算对git-am 是按行数读的算错一行它就把TARGET_DEVICES aes_nand那行默默截掉device 不进.targetinfodefconfig 选了等于没选——这种静默失败最坑debug 半天。后来老实用git format-patch让 git 自己算行数再没出过事。第二块0002是 config 补丁补 czz8888 那棵树它原本是给 HZHY MiniEVM 配的漏掉的、咱们 aes 板在 rk-forge 主线 U-Boot 下需要的几个开关。逐个说为什么。头一个是CONFIG_ARCH_MESONy这个最反直觉——咱们是 Rockchip 的板子开个 MesonAmlogic的 ARCH 干嘛因为它有个副作用把TEXT_OFFSET抬高。RK3506B 跑 OP-TEEOP-TEE 占着物理内存最底下的 secure 区0x0起一段如果内核的 zrel 清零区或页表落进这片 secure RAMStarting kernel...之后就是一个 data abort。开ARCH_MESON把内核加载偏移抬上去、避开 secure 区这个问题就没了。咱们 buildroot 那颗内核也用的同一招。这事其实和 rootfs Ch3 那颗伪装成 SFC abort 的 reserved-memory 坑是同一片 secure 区的两种表现——一个炸在用户态 dd 访问、一个炸在内核刚启动根子都是 OP-TEE 那块物理内存没留出来。然后是一组启动必需的RD_GZIP让内核能解压首启用的 gzip initramfsMTD_OF_PARTS把设备树里的固定分区实化成/dev/mtd0..6DEVTMPFS加DEVTMPFS_MOUNT让内核自动populate/dev——这两个尤其要紧因为首启置备的 ubiprog 要open(/dev/mtd5)没有 devtmpfs 它连设备节点都开不了ATAGS加ARM_ATAG_DTB_COMPAT是因为 rk-forge 的主线 U-Boot 用 ATAGS 传 bootargsparameter 里ATAG: 0x00200800得让内核兼容着把这些 ATAGS 转发进设备树。最后一个开关THUMB2_KERNEL是这块补丁里唯一故意不改、留在y的也是踩过坑才定的。OpenWrt 的 kmod 包全是 thumb2 编出来的如果手贱把运行内核的 THUMB2 关成 ARMvermagic 立刻对不上——kmodloader 拒绝加载板子上一堆模块加载失败。这个坑在板上日志里抓到了真实现场openwrt_done.txt 这轮 NAND 启动里kmodloader 报了满屏的 vermagic 错位crc32c_cryptoapi: version magic 7.1 SMP preempt mod_unload ARMv7 thumb2 p2v8 should be 7.1 SMP preempt mod_unload ARMv7 p2v8 kmodloader: 8 modules could not be probed注意看这两行 vermagic 的差别——模块那边多了个thumb2运行内核这边没有。这意味着那一轮镜像的运行内核被编成了 ARM 模式而 opkg 仓库里的 kmod 还是 thumb2于是 crc32c、ehci、scsi、usb-storage 这些模块全跪。系统照样能起来核心功能不依赖这些 kmod但 kmod 体系等于废了。对照看 openwrt_sd.txt 那轮 SD 启动kmodloader 老老实实done loading kernel modules一个 failed 都没有——那就是 THUMB2 留y之后该有的样子。所以补丁头里写明了这条THUMB2 必须留y别因为它看起来和 RK3506 无关就想关掉省事一关就是 vermagic 错配。构建OpenWrt 自建 musl 工具链分阶段 build架构定了进构建。scripts/build-openwrt.sh是这条 profile 的构建脚本但它干的第一件事就跟咱们 buildroot 那条路岔开了OpenWrt自己编一套 musl 工具链不借 rk-forge 那个 glibc 外部工具链。这又是 vermagic 逼的。kmod 的 vermagic 不光看内核.config还看编它用的 gcc——工具链一换vermagic 就变。rk-forge 的外部工具链是 Arm GNU 15.2、glibc 的OpenWrt 的 userspace 是 musl 的。硬把 glibc 工具链塞给 OpenWrt不光 musl userspace 跑不起来连 kmod 的 vermagic 都会和 opkg 仓库对不上。所以这块是故意分开的让 OpenWrt 用它自己的 musl 工具链编 kernel 和 kmodvermagic 才能和它的 opkg 仓库对得上。这跟咱们 rootfs Ch1 buildroot 借 glibc 外部工具链是相反的选择但两边的道理都成立——buildroot 那条不关心 kmod vermagic它根本没 kmod 体系OpenWrt 这条命根子就在 kmod 上。构建这步踩的坑最多挑三个最值得记的。第一个是make world -j14的跨阶段竞态。OpenWrt 的make world会把package/cleanup和target/linux/compile当成两个并行的 make[2] 作业一起跑结果package/cleanup在重新生成tmp/.packageinfo的时候target/linux那边正读这个文件——稳定挂报个target/linux failed to build还不带细节看着像 flaky 其实是必现的竞态。解法是不用make world改成分阶段 build每个阶段内部还是-j$(nproc)但阶段之间走严格顺序按 world 的依赖链tools/install → toolchain/install → target/linux/compile → package/compile → package/install → target/linux/install。这样阶段内照样并行阶段之间也不会撞上读写同一个文件。第二个是LINUX_DIR环境变量污染这个最阴。build-openwrt.sh 开头source lib/env.sh而 env.sh 里export LINUX_DIR指向的是 rk-forge 那棵已经打过补丁的 linux 树。偏偏 OpenWrt 的include/kernel.mk里写的是LINUX_DIR ? $(KERNEL_BUILD_DIR)/linux-$(LINUX_VERSION)——那个?是没设才设环境变量已经设了它就不设了于是 OpenWrt 拿着 rk-forge 那棵已经 quilt-apply 过的树再去 apply 一遍它自己的patches-7.1/补丁撞补丁0014/0016 一片 reject。解法很干脆build 那行加env -u LINUX_DIR把这个环境变量摘掉让 OpenWrt 自己把dl/linux-7.1.tar.gz解到它自己的build_dir/linux-7.1里再打补丁。第三个是 OpenWrtcmd()的静默假失败。OpenWrt 的 makefile 里有个cmd()宏默认走make -s还重定向文件描述符在-jN高并发下会把明明编成功的目标误判成失败——kernel 其实编出来了、产物在make 却报 error。解法是全程Vs绕开 cmd 的静默逻辑完整 verbose 输出落到 per-stage 日志失败了 tail 最后 30 行。这三个坑补完build 就稳了。产物在build_dir/target-*/linux-rockchip_rk3506/linux-7.1/下zImage约 7.27 MB、rk3506b-aes.dtb还有build_dir/.../root-rockchip/这棵 TARGET_DIR——它就是 rootfs 本体musl 的 busybox procd kmod 全在里面kmod 已经躺在lib/modules/了。后面 rk-forge 的 stage-rootfs 会把这棵树 rsync 到out/rootfs/和 buildroot 那条解 rootfs.tar 的路到这儿就岔开了。首启置备从 read-modify-write 升级到 from-source构建完进首启置备这块——怎么在第一次开机时把 rootfs 写进 NAND。这一步是接着 rootfs Ch3 那场 loader 弱写风波往下讲的建议 Ch3 和这篇对着看。先回忆 Ch3 的结论rkbin loader 写我们这份小 rootfs 时会把某些 erase blockPEB 3/4 那几个写弱首启读得出来、断电凉透再启就 ECC 不可纠、UBIFS 挂不上。Ch3 的解法是首启 initramfs 里跑一个ubiprog在 loader 写完、数据还新鲜的第一次 boot 时用 Linux 自己可靠的写路径把这些块重写一遍绕开 loader 的弱写。那个 ubiprog 是read-modify-write模式——读出每个块、擦掉、再写回去遇到整块 ECC 不可纠的就做页级恢复逐页读能纠的页保留、不能纠的填0xFF。OpenWrt 这条路能比 read-modify-write 更狠一档from-source。read-modify-write 不管怎么优化说到底还是从 NAND 读出 loader 写的可能已经弱的数据再写回去——它信任 NAND 上的存量。但 OpenWrt 的 rootfs 小musl9 MB 量级小到可以整个 gzip 之后塞进首启 initramfs、跟着 kernel 一起进 boot.img。这样首启时ubiprog 手里攥着一份从 host 打包出来的、确定的rootfs image在 RAM 里它就不需要再信任 NAND 上读出来的任何东西了——直接把整个 mtd5 擦干净从 RAM 里这份确定 image 一笔一笔写下去。board/aes/rootfs/ubiprog.c里这两个模式是靠参数分流的传一个 image 文件路径就是 from-source不传就是老的 read-modify-write。from-source 的循环逻辑直白得很——擦整个分区image 覆盖到的 PEB 从 RAM 文件读出来写下去image 之外的尾巴全擦成0xFFif(image_file){/* FROM-SOURCE: image 来自 RAM塞在首启 initramfs 里绝不回读 NAND。 * 擦整个分区再过 kernel 的可靠写路径把 image 写下去 * image 之外的 PEB 一律擦成 0xFF。一刀杀掉三种故障 * (1) 跨镜像残留——先烧 buildroot 再烧 OpenWrtNAND 尾巴里还留着 * 上一个更大 rootfs 的残骸UBIFS 挂上去就是新 index 旧残骸 * 的混合体recovery 失败、挂死 * (2) loader 的弱写——我们不再信任任何从 NAND 读回的数据 * (3) 页级 0xFF 恢复的 lossy——它会把落在不可纠 PEB 上的 UBIFS * index znode 也填成 0xFF损坏索引。*/为什么要比 read-modify-write 更狠因为 OpenWrt 这条 profile 多一个 read-modify-write 没有的麻烦跨镜像残留。OpenWrt rootfs 小9 MBbuildroot rootfs 大glibc23 MBbuildroot 那个 UBIFS 带 autoresize首启会把 174 MiB 分区撑满、写满所有 1392 个 PEB。要是先烧过 buildroot、再烧 OpenWrtOpenWrt 只覆盖前面几十个 PEB后面一千多个 PEB 还留着 buildroot 的残骸——UBIFS 下次挂上去读到的是新 index 指向的 旧残骸的混合体recovery 跑不完、直接挂死。from-source 把整个分区擦干净再写残留、弱写、lossy 恢复三个问题一起解决。那 buildroot 为什么不也用 from-source因为它用不了。buildroot rootfs 23 MBgzip 之后塞进 initramfsboot.img 会撑爆 16 MB 的 boot 分区。所以 buildroot 这条还是走 Ch3 那套 read-modify-write已经板上验过OpenWrt 这条才用得起 from-source。这个分流就实现在scripts/build-initramfs.sh里按ROOTFS_PROFILE切if[[${ROOTFS_PROFILE:-}openwrt]];thenROOTFS_UBI${OUT_DIR}/rootfs.ubi.imglog_infoembedding rootfs.ubi.img → rootfs.ubi.img.gz (openwrt from-source)gzip-c$ROOTFS_UBI$root/rootfs.ubi.img.gzelselog_infobuildroot profile — NOT embedding image (ubiprog read-modify-write)fiOpenWrt 这条把rootfs.ubi.imggzip 之后塞进 cpio9.5 MB 压到 3.34 MB压缩率 35%buildroot 那条一个字节都不塞、走老的 read-modify-write。同一个 ubiprog、同一份首启 initramfs 脚手架靠有没有这份内嵌 image 自动分流到两条置备路径——这是整个移植里最干净的一处设计。板验NAND from-source SD ext4两条都通设计讲够了上板看真东西。NAND 这条烧进去首启/init发现没有置备 marker就调 ubiprog 走 from-source 重写 mtd5。现场在 openwrt_done.txt[init] FIRST BOOT: ubiprog from-source rewriting /dev/mtd5 (9961472 B)… ubiprog: FROM-SOURCE /tmp/rootfs.ubi.img (9961472 B); erasing whole partition (1392 PEBs) ... 76 img PEBs written 1316 tail erased ubiprog done (from-source): wrote76 erased_tail1316 failed0 (of 1392 PEBs; image 9961472 B)wrote76 erased_tail1316这行值得细看9.5 MB 的 image 只占了 76 个 PEB剩下 1316 个 PEB 全被擦成0xFF——这就是 from-source 杀跨镜像残留的现场buildroot 当年撑满的那一千多个 PEB一个不留全擦了。写完重新 attachUBIFS 干干净净挂上ubi0: attached mtd5 (name rootfs, size 174 MiB) ubi0: good PEBs: 1392, bad PEBs: 0, corrupted PEBs: 0 UBIFS (ubi0:0): UBIFS: mounted UBI device 0, volume 0, name rootfs [init] provisioning complete → switch_rootbad PEBs: 0、UBIFS 一次 mount 成功、没有 recovery——对照 Ch3 那场动不动error -74、recovery 半天、页级恢复的风波这里安静得不像同一个 NAND。switch_root 之后 procd 三段起来落进 OpenWrt 的 shellprocd: - early - procd: - ubus - procd: - init - _______ ________ __ | |.-----.-----.-----.| | | |.----.| |_ | - || _ | -__| || | | || _|| _| |_______|| __|_____|__|__||________||__| |____| |__| W I R E L E S S F R E E D O M ----------------------------------------------------- OpenWrt 24.10-SNAPSHOT, r0-31d15c0 rootOpenWrt:~#到这里 OpenWrt userspace 起来了busybox 是它自己的 musl 版v1.36.1 ... built-in shell (ash)不是 buildroot 那个。NAND 路这份日志里那批 vermagic 错位前面 config 那节贴过就是 THUMB2 配错那轮的现场是这条 profile 调通过程中真实踩过的坑、不是合成出来的演示。SD 这条路更要夸一句——它几乎没写新代码。forge assemble --rootfsopenwrt --sd直接复用 buildroot 那条已经验过的 pack-sd把 OpenWrt 的 TARGET_DIR 喂给mke2fs -d出一份 ext4 rootfsboot-sd.img 当 kernel组一个 RKFW 的 update-sd.img本板 ROM 只认 RK-tool 卡不认裸 dd。kernel 拿着root/dev/mmcblk0p3挂上 ext4procd 起来就是 OpenWrt shellopenwrt_sd.txtmmcblk0: mmc0:b36a SDABC 58.2 GiB mmcblk0: p1 p2 p3 EXT4-fs (mmcblk0p3): mounted filesystem ... r/w with ordered data mode. VFS: Mounted root (ext4 filesystem) on device 179:3. VFS: Pivoted into new rootfs procd: - early - procd: - init - kmodloader: done loading kernel modules from /etc/modules.d/* OpenWrt 24.10-SNAPSHOT, r0-31d15c0SD 这条 kmodloader 全程done loading没一个 could not be probed——THUMB2 留y之后就该这样。两条路、两份板上日志摆出来OpenWrt 真能在 RK3506 上跑起来这件事算是验过了。加包、改 rootfs还有 WiFi 得说实话板子跑起来了接下来才是 OpenWrt 的正戏——加包。入口是board/aes/openwrt/aes-nand.config这是给 OpenWrtmake defconfig用的 seed。想加 LuCI加一行CONFIG_PACKAGE_luciy想加别的包同理包名在 OpenWrt 树里make menuconfig查。改完跑forge build --rootfsopenwrt --reconfigure那个--reconfigure让 OpenWrt 从 seed 重新展开.config别忘了否则它还用老的再forge packforge assemble链路自己往下走。这里有个 WiFi 的事得如实交代不能藏着。现在这版 seed 里DEVICE_PACKAGES只选了kmod-usb2 kmod-usb-storage没选 RTL8733BU 的 kmod所以 OpenWrt 这条 profile 上WiFi 栈目前只到 cfg80211 子系统这一层没到芯片驱动。板上日志里能看到的 WiFi 相关输出只有这么一行cfg80211: failed to load regulatory.db这是无线监管数据库没打进 rootfs不挡 boot但也说明这一轮镜像里 WiFi 没真正接起来。要真在 OpenWrt 上用 RTL8733BU得把它的 kmod 包选进aes-nand.configOpenWrt 走 kmod 包体系不走 buildroot 那套fetch-rtl8733bu-driver.sh的做法。WiFi 在 buildroot profile 上是板上 probe 验过的见 peripherals Ch3OpenWrt 这边还是个待办这里如实标出来。还没干的诚实交代这条 profile 还有几样没收尾的活列在这免得给读者一个全齐了的错觉。头一样是源码归属。现在pins/openwrt还指着czz8888个人仓依赖个人仓库做构建心里总不踏实。计划 fork 一份到工作室 org和pins/rtl8733bu一个模式fork 是原样镜像、不做仓内改动pins/openwrt改指 fork。第二样是 NAND rootfs 的形态。现在 Phase 1 的是 UBIFS 可写根直接复用 pack-ubifs图的是最快跑通。OpenWrt 在 NAND 上的标准方案其实是squashfs-on-UBI——只读的 squashfs 根加一个可写的 overlay 卷配 sysupgrade 支持恢复出厂。这套要新写一个pack-squashfs-ubi.sh是 Phase 2 的活。第三样是 buildmeter 的进度可视化还没加 openwrt parserforge Ch 那套构建进度条现在 OpenWrt build 阶段还是裸输出。Phase 1 能复用 buildmeter 的 kindkernel 解析不急。成功长这样整条路走通NAND 首启从 ubiprog 重写 mtd5到 OpenWrt shell 落地就是上面贴的那段——wrote76 erased_tail1316 failed0UBIFS 一次挂上procd 起来落进rootOpenWrt:~#。SD 那条复用现成 pack-sdext4 挂上、pivot root、同样落进 OpenWrt shell。buildroot profile 没动一丝一毫不加--rootfs还是走 buildroot 那条验过的路OpenWrt 是和它并排多出来的一条 profileforge all --rootfsopenwrt一条命令从源码到 update.img。RK3506 上能跑一套真能 opkg、能现场加 kmod 的发行版了——这事儿在 rk-forge 之前没人做过。给板子拍张照不过分。