这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 综合技术 » 基础知识 » OrangePiZero2W系统

共2条 1/1 1 跳转至

OrangePiZero2W系统

菜鸟
2026-08-31 16:04:49     打赏


Orange Pi Zero 2W 自己拼一套系统:主线内核 7.2.2 + debootstrap Debian,从开坑到点亮

流水账,按实际顺序记的,包括走错的路。最后是点亮了的,但中间在一个坑里泡了好几个小时。

开坑时定的调子:U-Boot 用编译好的,内核和 dts 拿源码自己编,rootfs 随便。目标就是搞明白每一层在干嘛,不用 Armbian 整包。

主机是 x86_64 Ubuntu,板子是 Zero 2W(Allwinner H618,1GB)。


0. 先把思路捋直

拆三块,各自独立产出,最后拼到 SD 卡上:

  • U-Boot → 烧到卡的 8KB 偏移

  • 内核 + DTB → 自己编

  • rootfs → debootstrap 造个 Debian

开工前先确认了一个事:dts 和内核是不是耦合的? 答案是强耦合。dts 里 #include <dt-bindings/clock/sun50i-h616-ccu.h> 这种引用的是内核树里的头文件,make dtbs 本身就是内核构建的一部分;运行时还要靠 dtb 里的 compatible 字符串和内核驱动的 of_match_table 对上。所以内核和 dts 必须同一份源码树编,别一个用 A 版本一个用 B 版本。

反过来 U-Boot 和内核基本解耦——U-Boot 只管把 Image 和 dtb 读进内存再跳转,不关心内核是 6.x 还是 7.x。所以"U-Boot 用现成的"这条路是成立的。


1. 内核:从 kernel.org 挖到完整的 clone 命令

这里我特意没直接抄命令,而是走了一遍"假如什么都不知道怎么找出来":

  1. 板子名 → 搜规格 → Allwinner H618、arm64。这两个词是后面所有参数的来源(ARCH=arm64、aarch64-linux-gnu-、主线里叫 sun50i-h616/h618)。

  2. 去 kernel.org 首页看发布列表,那一行 稳定的 7.2.2 2026-08-28,后面有一堆链接。

  3. 点那行的「浏览」进 cgit——clone URL 就明明白白写在 cgit 页面上,不用手打;顺便在网页上直接翻目录,确认 arch/arm64/boot/dts/allwinner/ 里有没有我的板子。

翻到了:

sun50i-h618-orangepi-zero2w.dts   4042

4042 字节,主线自带,不用自己写 dts。 4. 版本号 7.2.2 → git tag 就是加个 v 前缀 v7.2.2。

拼出来:

git clone --depth=1 
  https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git 
  -b v7.2.2 linux-7.2.2

clone 完发现拉下来的是整棵树,几千个跟我毫无关系的 dts 全在里面。这个是正常的,内核不按板子切分发布,谁都拉同一份,只编自己那一个就行。

defconfig 叫什么,自己 ls 出来

l@l:~/my_opizero2w/linux-7.2.2$ ls arch/arm64/configs/
defconfig  hardening.config  virt.config

就一个。arm64 主线不做板级 defconfig,这一个通用的已经把 sunxi 平台涵盖了。另外两个是可选叠加片段,不管。

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig

编译前先 grep 三个救命选项

CONFIG_MMC_SUNXI=y
CONFIG_SERIAL_8250_DW=y
CONFIG_EXT4_FS=y

全是 =y,defconfig 默认就对,不用改。

这三个必须 =y 不能 =m,我一开始理解反了,以为 =m 就是"跟内核没关系、是 rootfs 那边的东西"。正好相反:

内核要挂 rootfs
  → 需要 MMC 驱动才能读 SD 卡
      → 但驱动是 .ko,放在 /lib/modules/ 里
          → 而 /lib/modules/ 在 rootfs 里
              → rootfs 还没挂上!挂它才需要这个驱动
                  → 死锁,panic

=y 的才是"焊死在 Image 里、跟 rootfs 无关"的;=m 的 .ko 恰恰躺在 rootfs 里。而且 =m 的模块不是"另外搞的",它就是这次编译的产物,make modules 编出 .ko、make modules_install 搬进 rootfs,.ko 里刻着版本号,对不上内核拒绝加载。

(=n 在 .config 里长这样:# CONFIG_XXX is not set。初期别急着关,先跑通。)

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image dtbs modules -j$(nproc)

编完不看日志,直接查产物:

-rw-rw-r-- 1 l l  40M  arch/arm64/boot/Image
-rw-rw-r-- 1 l l  23K  arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dtb

内核这块搞定。


2. rootfs:debootstrap

先补 qemu

l@l:~$ which debootstrap qemu-aarch64-static
/usr/sbin/debootstrap

debootstrap 有,qemu 没有。为什么要它:我在 x86 上造 arm64 的 rootfs,debootstrap 第二阶段要实际运行 arm64 程序(跑各个 deb 包的 postinst 配置脚本),x86 CPU 执行不了 arm64 指令,得靠 qemu-user 做用户态翻译。

$ sudo apt install qemu-user-static binfmt-support
虚拟软件包 qemu-user-static 由下面的软件包提供:
  qemu-user-binfmt-hwe
  qemu-user-binfmt
错误: 软件包 qemu-user-static 没有可安装候选

新系统上包名拆了,选标准版:

sudo apt install qemu-user-binfmt binfmt-support

装完验证,注意新版可执行文件名没有 -static 后缀了:

$ which qemu-aarch64-static qemu-aarch64
/usr/bin/qemu-aarch64
$ ls /proc/sys/fs/binfmt_misc/ | grep aarch64
qemu-aarch64
qemu-aarch64_be

binfmt 里有条目才是关键——内核遇到 arm64 二进制会自动转交 qemu,chroot 进去是透明的。

顺带搞清了一个概念:qemu-user 只能让 x86 跑单个 arm64 程序,够我 chroot 进去装包配置;它不模拟 CPU 之外的东西,跑不了内核,"开不了机"。真正启动是板子的事。(要在电脑上启动整机得用 qemu-system-aarch64,那是另一码事,我用不到。)

两阶段

cd ~/my_opizero2w
sudo mkdir -p rootfs
sudo debootstrap --arch=arm64 --foreign bookworm rootfs http://deb.debian.org/debian

注意 rootfs 是参数不是当前目录,人站在外面往里装。要是先 cd rootfs 再敲就套娃成 rootfs/rootfs/ 了。

第一阶段完了 rootfs 里目录结构齐了(bin boot dev etc home lib ... usr var),但这只是解压完,不等于装好。deb 包的安装分两个动作:解压 + 跑 postinst 配置脚本(建用户组、生成配置、设 alternatives、跑 ldconfig、更新 dpkg 数据库)。跳过第二阶段拿到的是"文件都在但没配置完"的半成品。

而那些 postinst 和它们调用的 dpkg/ldconfig 全是 arm64 二进制,所以第二阶段必须靠 qemu 跑:

sudo cp /usr/bin/qemu-aarch64 rootfs/usr/bin/
sudo chroot rootfs /debootstrap/debootstrap --second-stage
I: Configuring libc-bin...
I: Base system installed successfully.

这次才是真的装完。

modules_install

rootfs 目录有了,回头把内核模块灌进去:

cd ~/my_opizero2w/linux-7.2.2
sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- 
  INSTALL_MOD_PATH=/home/l/my_opizero2w/rootfs 
  modules_install

INSTALL_MOD_PATH 不给会污染自己电脑的 /lib/modules。而且 sudo 下 ~ 可能解析成 root 的家目录,用绝对路径

最后一行 DEPMOD /home/l/my_opizero2w/rootfs/lib/modules/7.2.2 说明 depmod 跑过了,依赖表生成了。


3. chroot 进去配置

这里先卡了一下,因为我以为账号密码这些"是系统开机自己设的"。不是——平时装 Ubuntu 时问你用户名密码的是安装程序,debootstrap 这条路上根本没有安装程序,那些活得自己补。不配的话烧进去开机是能开,但你被关在门外(root 密码空/锁,也没 ssh)。

cd ~/my_opizero2w
sudo chroot rootfs

第一个小坑: 我把三行一起粘了:

l@l:~/my_opizero2w$ cd ~/my_opizero2w
sudo chroot rootfs
passwd
root@l:/# 1
bash: 1: command not found

chroot 会起一个新 shell,跟在后面的 passwd 被外层 shell 提前吞了,我输的密码变成了命令。进 chroot 之后的命令必须等提示符变了再单独敲。

重来:

root@l:/# passwd
passwd: password updated successfully
root@l:/# apt update
Hit:1 http://deb.debian.org/debian bookworm InRelease
All packages are up to date.

装东西:

apt install -y systemd-sysv udev ifupdown iproute2 openssh-server sudo net-tools
useradd -m -s /bin/bash l
passwd l
usermod -aG sudo l
echo "opizero2w" > /etc/hostname
echo "127.0.1.1 opizero2w" >> /etc/hosts

ssh 装的时候刷出 invoke-rc.d: could not determine current runlevel ——吓人但正常,chroot 里 systemd 没真跑,确定不了运行级别。开机自启的符号链接已经建好了(Created symlink .../multi-user.target.wants/ssh.service),板子上开机会正常拉起。

顺手预装:vim + mihomo

想到既然 chroot 里能 apt,干脆把常用的一次装了,省得板子起来再折腾。

问了一下边界,结论是:

在 chroot 里做有效吗
装普通软件(vim/python/nginx…)✓ 推荐
systemctl enable(建符号链接)✓ 板子开机生效
systemctl start / restart✗ 系统没真跑
装内核、加载模块、读硬件✗ 借的是主机 x86 内核

判断标准就是:纯软件文件的操作有效,和运行中内核/硬件交互的操作无效。

mihomo 官方源里没有,但我主机 ~/下载 里正好有个 mihomo-linux-arm64-v1.19.30.deb,架构对得上。拷进去装:

# 主机上
sudo cp ~/下载/mihomo-linux-arm64-v1.19.30.deb ~/my_opizero2w/rootfs/tmp/
# chroot 里(本地 deb 用 dpkg -i,不是 apt install)
dpkg -i /tmp/mihomo-linux-arm64-v1.19.30.deb
Setting up mihomo (1.19.30) ...

Go 静态二进制,没缺依赖。dpkg -L mihomo 看它装哪了:

/etc/mihomo/config.yaml               ← 配置(现在是空模板)
/usr/bin/mihomo
/usr/lib/systemd/system/mihomo.service

中途冒出来的疑问:chroot 里是怎么联网的?

我在 chroot 里啥网络都没配,apt 却能通。搞明白了:chroot 只换了根目录,网络那层压根没碰,里面的程序直接用主机的网卡、IP、路由,跟主机上开个新终端跑 apt 没区别。

它其实是个很浅的隔离,只隔离文件系统;进程、用户、网络都不隔离(对比 Docker/虚拟机才有独立网络栈)。唯一相关的是 DNS——程序读的是 rootfs/etc/resolv.conf,debootstrap 已经帮忙填好了,所以域名解析也正常。

也顺带纠正了自己的一个混淆:我装的 ifupdown 跟"chroot 联网"一点关系没有,它只是躺在 rootfs 里的文件,是给板子将来联网用的,现在没运行。

nmcli 是哪来的 → 换 NetworkManager

想用 nmcli 结果没有。查了一下它属于 network-manager 包,我压根没装过。(顺带记一个:查某个命令属于哪个包用 dpkg -S 命令名。)

Debian 上管网络的方案是几选一,别混用:ifupdown(传统,编 /etc/network/interfaces)、NetworkManager(nmcli/nmtui)、systemd-networkd。想着板子可能要连 WiFi,换 NM 方便,那 ifupdown 就没必要留着占位置:

apt install -y network-manager
apt purge -y ifupdown
rm /etc/network/interfaces      # purge 没删干净,残留文件手动清
systemctl enable NetworkManager

(purge 时刷了一堆 perl: Setting locale failed、E: Can not write log (Is /dev/pts mounted?),都是 chroot 环境的无害噪音,操作是成功的。)


4. 岔路:板载 WiFi 用不了

正要装 WiFi 固件时我提了个问题:驱动不是在内核里或者是内核模块吗,还装什么固件?

这里学到东西了,驱动 ≠ 固件,是两样:

  • 驱动(在内核里 =y 或 .ko)= 知道"怎么操作这颗芯片"的代码

  • 固件(/lib/firmware/ 下的二进制 blob)= 要灌进芯片内部、让芯片自己跑起来的微码

WiFi 芯片本身是个小处理器,开机时驱动去 /lib/firmware/ 读固件文件灌进去,芯片才活。找不到固件,驱动再全也是对着一块死硅片。固件之所以单独装,是因为多是闭源私有二进制,不能进 GPL 内核树。

(GPIO、I2C、SPI、UART、SD 控制器这类没有内部处理器的简单外设不需要固件。)

然后查了下 Zero 2W 的 WiFi 芯片,坏消息:模块是 AW859A,内部是 Unisoc(紫光展锐)UWE5622 / sc2355。这颗芯片主线内核根本没有驱动(sprdwl_ng / uwe5622_bsp_sdio 只在全志 BSP 里)。所以问题不是缺固件,是缺驱动,装什么 firmware-* 包都白搭。

摆在面前的路:

  • A:走有线(24pin 扩展板的以太网,或 USB 有线网卡)

  • B:买颗主线原生支持的 USB WiFi(mt7601u、rtl8188 之类)

  • C:硬啃 out-of-tree 的 uwe5622 驱动——给 5.4/6.1 BSP 内核写的,往 7.2.2 上移植 API 变了一大堆,而且这驱动本身有名的烂(跑一段时间 WiFi 就失效要重载模块)

我选了第四条:USB Gadget。 不用买任何硬件,一根 USB-C 线把板子和电脑连起来,板子模拟成一块 USB 网卡组网。而且这功能主线原生支持,跟我的路线完全契合。

先 grep 一下内核支持够不够:

CONFIG_USB_GADGET=y
CONFIG_USB_MUSB_HDRC=y
CONFIG_USB_MUSB_SUNXI=y          ← 全志的 USB 控制器驱动,最关键的一块
CONFIG_USB_MUSB_DUAL_ROLE=y      ← 双角色,涵盖 device 能力
CONFIG_USB_CONFIGFS=m
CONFIG_USB_CONFIGFS_NCM=y
CONFIG_USB_CONFIGFS_ECM=y
CONFIG_USB_CONFIGFS_RNDIS=y
# CONFIG_USB_ETH is not set

够了,内核不用重编。(USB_ETH 没开无所谓,那是老式写死的 gadget 方式,我走 configfs 这条新路。MUSB_GADGET/MUSB_HOST 单独没设也无所谓,DUAL_ROLE=y 已经涵盖。)

gadget 具体配置是运行时的事,板子能开机之后再弄——那时能实时看 dmesg,比现在盲写脚本靠谱十倍。先放着。


5. U-Boot:从 Armbian 板子上抠

主线 U-Boot 官方只发源码,自己编要先编 ATF 拿 bl31.bin。既然定了"用编译好的",那现实途径只有一个:从别人做好的镜像/系统里抠。

我手上正好有另一块跑着 Armbian 的 Zero 2W(通过 USB gadget 网络 ssh 连着,192.168.137.2)。Armbian 把 U-Boot 作为 deb 包装在系统里:

root@orangepizero2w:/usr/lib# ls -lh /usr/lib/linux-u-boot-current-orangepizero2w/
-rw-rw-r-- 1 root root 867K u-boot-sunxi-with-spl.bin      ← 就是它
-rw-rw-r-- 1 root root  56K u-boot-config-target-1
...

867K,SPL + ATF + U-Boot 本体打包在一起。scp 回主机:

scp root@192.168.137.2:/usr/lib/linux-u-boot-current-orangepizero2w/u-boot-sunxi-with-spl.bin ~/my_opizero2w/

区分一下:/usr/lib/ 下这个是文件形式的备份;真正在跑的那份烧在 SD 卡 8KB 偏移的裸区,不在任何分区任何目录,ls 看不见,只能 dd 读。

版本上不匹配没关系——那台 Armbian 跑的是 6.18.45 BSP 内核,我的是主线 7.2.2,但 U-Boot 只管加载和跳转,不关心内核版本。

顺手抄 Armbian 的作业

趁它还活着,把范本扒下来:

root@orangepizero2w:~# fdisk -l /dev/mmcblk0
Device         Boot Start      End  Sectors  Size Id Type
/dev/mmcblk0p1       8192 61702143 61693952 29.4G 83 Linux

就一个分区,从 8192 扇区(4MB)开始。/boot 只是这个 ext4 分区里的一个普通目录。

root@orangepizero2w:~# ls -lh /boot/
armbianEnv.txt   boot.cmd   boot.scr   dtb -> dtb-6.18.45-current-sunxi64
Image -> vmlinuz-6.18.45-current-sunxi64    uInitrd    ...

它用 boot.scr(boot.cmd 用 mkimage 编出来的),不用 extlinux。cat /boot/boot.cmd 是一大坨,overlay / fixup / uInitrd 逻辑占了九成,我全都不需要。核心其实就最后几行:

load ${devtype} ${devnum} ${fdt_addr_r} ${fdtdir}/${fdtfile}
fdt addr ${fdt_addr_r}
fdt resize 65536
load ${devtype} ${devnum} ${kernel_addr_r} ${prefix}Image
booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}

bootargs 的核心是 root=... rootwait rootfstype=ext4 console=ttyS0,115200。dtb 放在 /boot/dtb/allwinner/ 下。

记下这几行,后面救了我一命(尤其 fdt addr 那句,我第一版脚本漏了)。


6. 装配 SD 卡

有第二张卡,所以 Armbian 那张原样留着当救援/参考环境,Debian 烧新卡。

l@l:/dev$ lsblk
sda           8:0    1  62.5G  0 disk           ← 新卡(读卡器,RM=1)
nvme0n1     259:0    0 953.9G  0 disk           ← 系统盘,别碰
├─nvme0n1p5 259:5    0 302.8G  0 part /

分区

照抄 Armbian 的单分区布局:

l@l:/dev$ sudo fdisk /dev/sda
命令: o        (新建空 DOS 分区表)
命令: n → p → 1
第一个扇区 (2048-131074047, 默认 2048): 8192      ← 关键,别用默认
最后一个扇区: (回车,用满)
命令: w
l@l:/dev$ lsblk /dev/sda
sda      8:0    1 62.5G  0 disk
└─sda1   8:1    1 62.5G  0 part

这里我卡了一下:不是说要两个分区吗?前面 8MB 和后面?

搞混了。前面那 4MB 不是分区,是裸区

扇区 0    ─┐
          │  前 4MB:裸区(分区表里没登记,没有文件系统)
扇区 16   │    ← U-Boot 烧在这(8KB = 16 扇区偏移)
扇区 8191 ─┘
扇区 8192 ─┐
          │  sda1:唯一的分区,ext4,整个 rootfs
          │        /boot 只是它里面的一个目录
卡尾      ─┘

为什么 U-Boot 必须在裸区:芯片上电跑的是固化在硅片里的 BROM,它不认识 ext4、不认识 FAT、不懂"文件"这个概念,只会去一个写死的扇区位置读数据当引导程序执行。

那"教科书上的 FAT boot + ext4 root 双分区"呢?那是为了应付"引导只能读 FAT"的老限制(树莓派就是这样)。全志平台 U-Boot 能直接读 ext4,而且 Armbian 就是单分区跑通的,那就单分区,少一个分区少一处出错。

格式化 + 烧 U-Boot

l@l:/dev$ sudo mkfs.ext4 -L opi-root /dev/sda1
文件系统 UUID:ddf6a995-0e31-4b53-92a8-ae703787a870      ← 记下来,后面要用
l@l:/dev$ sudo dd if=~/my_opizero2w/u-boot-sunxi-with-spl.bin of=/dev/sda bs=1024 seek=8 conv=notrunc,fsync
866+1 records in
866+1 records out
887113 bytes (887 kB, 866 KiB) copied, 0.137967 s, 6.5 MB/s

of=/dev/sda 是整卡,不带数字——这是全程唯一一处用整卡设备的地方,因为裸区不属于任何分区,只能"整卡 + 偏移"访问。写成 sda1 就写进 ext4 文件系统内部了,BROM 根本不去那找。

(顺带把 sda / sda1 彻底分清:不带数字 = 整块物理设备,包含分区表 + 所有分区 + 裸区;带数字 = 里面的第 N 个分区。烧 U-Boot 用 sda,格式化和放文件用 sda1。)

灌 rootfs、内核、dtb

l@l:/dev$ sudo mount /dev/sda1 /mnt/sdcard
l@l:/dev$ sudo rsync -aHAX --info=progress2 ~/my_opizero2w/rootfs/ /mnt/sdcard/
  1,065,714,203  99%  842.04MB/s    0:00:01 (xfr#16620, to-chk=0/21323)

1GB、21323 个文件。-a 保权限属主符号链接、-H 硬链接、-A ACL、-X 扩展属性;源路径末尾那个斜杠不能漏(拷"里面的内容"而不是目录本身)。

sudo cp ~/my_opizero2w/linux-7.2.2/arch/arm64/boot/Image /mnt/sdcard/boot/
sudo mkdir -p /mnt/sdcard/boot/dtb/allwinner
sudo cp ~/my_opizero2w/linux-7.2.2/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dtb 
        /mnt/sdcard/boot/dtb/allwinner/

记住这两条 cp 是独立于 rsync 的步骤——后面换卡时我就栽在这上面了。

boot.cmd v1

cat > /tmp/boot.cmd << 'EOF'
setenv kernel_addr_r "0x40080000"
setenv fdt_addr_r    "0x4FA00000"
setenv bootargs "root=UUID=ddf6a995-... rootwait rootfstype=ext4 console=ttyS0,115200 earlycon=uart,mmio32,0x05000000 loglevel=8"
load ${devtype} ${devnum}:1 ${kernel_addr_r} /boot/Image
load ${devtype} ${devnum}:1 ${fdt_addr_r} /boot/dtb/allwinner/sun50i-h618-orangepi-zero2w.dtb
booti ${kernel_addr_r} - ${fdt_addr_r}
EOF

sudo apt install u-boot-tools
sudo mkimage -C none -A arm64 -T script -d /tmp/boot.cmd /mnt/sdcard/boot/boot.scr

fstab(单分区就一行)、sync、umount,插板子,接串口(115200),上电。


7. 首启:卡在 dtb 上,泡了几个小时

第一次:Did not find a cmdline Flattened Device Tree

Hit any key to stop autoboot: 0
Scanning mmc 0:1...
Found U-Boot script /boot/boot.scr
796 bytes read
## Executing script at 4fc00000
41617920 bytes read in 1698 ms (23.4 MiB/s)      ← 内核加载 OK(正好 40M)
22730 bytes read in 3 ms (7.2 MiB/s)             ← dtb 加载 OK(正好 23K)
Moving Image from 0x40080000 to 0x40200000, end=0x42a80000
ERROR: Did not find a cmdline Flattened Device Tree
Could not find a valid device tree
SCRIPT FAILED: continuing...

串口通了,U-Boot 跑起来了,脚本也找到执行了,内核和 dtb 字节数都完全正确——就是 booti 那一刻找不到 dtb。

对照 Armbian 的 boot.cmd 发现我少了 fdt addr 那句:只把地址当参数传给 booti 不够,得显式告诉 U-Boot"这个地址是有效 dtb,认它"

中途的困惑:U-Boot 把内核移动了?后面不是 rootfs 吗?

看到 Moving Image from 0x40080000 to 0x40200000 我懵了——内核不是在 rootfs 里躺着吗,怎么被移动了?后面那片不是 rootfs 吗?

搞混了内核的两个身份:

【SD 卡上,静止不动】/boot/Image ← 40M 静态文件,从头到尾一动没动
        │  U-Boot 执行 load,把它读进内存 0x40080000
        ▼
【内存里】U-Boot 发现内核要放在 0x40200000 才能正确解压运行
        │  于是在 RAM 里把这份副本挪过去    ← "Moving Image" 说的是这个
        ▼
【CPU 跳转执行内存里的内核】
        │  内核初始化完,根据 bootargs 的 root=
        ▼
【回过头去挂载 SD 卡上的 rootfs 作为 /】

那几个 0x4... 全是内存地址,不是卡上的位置。rootfs 是内核跑起来之后才挂的。

第二次(v2,加了 fdt addr,改用内置变量):BADMAGIC

libfdt fdt_check_header(): FDT_ERR_BADMAGIC
No FDT memory address configured. Please configure the FDT address via "fdt addr <address>" command.
Aborting!
Moving Image from 0x40080000 to 0x40200000, end=0x42a80000
ERROR: Did not find a cmdline Flattened Device Tree

报错变具体了。当时的判断是 ${fdt_addr_r} 这个内置变量是空的。

第三次(v3,改回显式地址 + 自定义变量名 + fdt addr):还是 BADMAGIC

一模一样的错。到这儿我意识到不该再盲改脚本烧卡了——一轮要断电、拔卡、插主机、改、重编、卸载、插回、上电,太慢

停进 U-Boot 命令行手动敲

上电时看到 Hit any key to stop autoboot 狂按回车:

=> printenv kernel_addr_r
kernel_addr_r=0x40080000
=> printenv fdt_addr_r
fdt_addr_r=0x4FA00000

变量是有值的,而且正是我用的地址。之前那个"变量为空"的判断是错的,排除。

=> load mmc 0:1 ${fdt_addr_r} /boot/dtb/allwinner/sun50i-h618-orangepi-zero2w.dtb
22730 bytes read in 3 ms (7.2 MiB/s)
=> md ${fdt_addr_r} 4
4fa00000: 3ace720e 6875f891 cd02edaf 9fb96730  .r.:..uh....0g..

真凶。 有效 dtb 的开头必须是魔数 d00dfeed,这里是 3ace720e,一坨乱码。

字节数完全对(22730),说明文件确实被读出来了,但内容是垃圾

这十分钟顶前面三轮烧卡。

换卡 + 一堆 I/O 错误

那张卡之前烧系统就"只能开机一次",我怀疑有坏块,换了张卡。结果新卡也不省心:mkfs.ext4 反复报 输入/输出错误、device offline,dd 清零重新分区格式化才成功;后面 sudo sync && sudo umount 还直接卡死,Ctrl+C 完全无效(D 状态不可中断睡眠)。

停下来先测卡靠不靠谱,别在坏卡上继续耗:

sudo dd if=/dev/urandom of=/mnt/sdcard/testfile bs=1M count=50 conv=fsync
sync; sudo md5sum /mnt/sdcard/testfile
sudo umount /mnt/sdcard && sudo mount /dev/sda1 /mnt/sdcard   # 强制丢弃页缓存,从物理卡重读
sudo md5sum /mnt/sdcard/testfile
a3a1afc6cb6faae823e441b8eb30e6fe  /mnt/sdcard/testfile
a3a1afc6cb6faae823e441b8eb30e6fe  /mnt/sdcard/testfile

两次一致,卡能用,之前那些 I/O 错误大概率是读卡器接触松动。

卸载重挂这步是关键,不然 md5sum 读的可能是内存里的页缓存,坏块根本暴露不出来。

换卡带出来的两个新雷

  1. UUID 全变了。 新卡是 ff40800a-22de-4f8f-9717-d571971971aa,但 boot.cmd 和 /etc/fstab 里写的还是旧卡的 ddf6a995...。这么烧下去内核找不到根设备,必 panic。

  2. 内核和 dtb 根本没拷过去。 换卡后我只重跑了 rsync rootfs,忘了那两条独立的 cp:

l@l:/dev$ ls -lh /mnt/sdcard/boot/Image
ls: cannot access '/mnt/sdcard/boot/Image': No such file or directory
ls: cannot access '/mnt/sdcard/boot/dtb/allwinner/': No such file or directory
-rw-r--r-- 1 root root 727 /mnt/sdcard/boot/boot.scr

破案:源 dtb 是好的

l@l:/dev$ xxd ~/my_opizero2w/linux-7.2.2/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dtb | head -1
00000000: d00d feed 0000 58ca 0000 0038 0000 52ac  ......X....8..R.

d00d feed ✓,后面 0000 58ca = 22730 字节,正好对上文件大小。

结论:源文件完好,是旧卡有坏块,dtb 恰好落在坏块上,写进去就损坏了。 这也解释了旧卡"只能开机一次"。

所以前面几轮改 boot.cmd 的方向部分错了——fdt addr 那句该加(不加确实报错),但真正的病根是坏卡,不是脚本。

重新拷,拷完当场验(卸载重挂再读,绕过页缓存):

l@l:/dev$ sudo cp .../Image /mnt/sdcard/boot/
l@l:/dev$ sudo mkdir -p /mnt/sdcard/boot/dtb/allwinner
l@l:/dev$ sudo cp .../sun50i-h618-orangepi-zero2w.dtb /mnt/sdcard/boot/dtb/allwinner/
l@l:/dev$ sync; sudo umount /mnt/sdcard; sudo mount /dev/sda1 /mnt/sdcard
l@l:/dev$ sudo xxd /mnt/sdcard/boot/dtb/allwinner/sun50i-h618-orangepi-zero2w.dtb | head -1
00000000: d00d feed 0000 58ca 0000 0038 0000 52ac  ......X....8..R.
l@l:/dev$ ls -lh /mnt/sdcard/boot/Image
-rw-r--r-- 1 root root 40M /mnt/sdcard/boot/Image

卡上这份也是 d00d feed,和源文件一字节不差。BADMAGIC 到此终结。


8. 第二个坑:内核起来了,但挂不上根分区

改好新 UUID(v4),重编 boot.scr,改 fstab,上电:

[    1.517703]  squashfs
[    1.519637]  vfat
[    1.525331] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
[    1.533589] CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.2.2 #1 PREEMPT
[    1.540806] Hardware name: OrangePi Zero 2W (DT)

panic 了,但这其实是巨大进展:Not tainted 7.2.2 + Hardware name: OrangePi Zero 2W (DT) —— 我编的主线内核跑起来了,dtb 也生效了(不然不会认出板子型号)。

问题只剩最后一步:unknown-block(0,0),找不到根设备。日志里能看到它支持 ext4,所以不是文件系统类型的问题。

根因:没有 initramfs 的情况下,内核在挂根分区这个阶段不认识 root=UUID=...。 解析 UUID 靠的是 initramfs 里的工具,我走的是极简路线没有 initramfs,就只能用设备路径。

一行改动(v5):

root=UUID=ff40800a-...   ✗
root=/dev/mmcblk0p1      ✓

fstab 也一并改成 /dev/mmcblk0p1。


9. 点亮

[  OK  ] Started NetworkManager.service - Network Manager.
[  OK  ] Started ssh.service - OpenBSD Secure Shell server.
[  OK  ] Reached target multi-user.target - Multi-User System.

Debian GNU/Linux 12 opizero2w ttyS0

opizero2w login: l
Password:
Linux opizero2w 7.2.2 #1 SMP PREEMPT Sun Aug 30 21:15:25 CST 2026 aarch64

l@opizero2w:/$ ls
bin   dev  home  lost+found  mnt  proc  run   srv  tmp  var
boot  etc  lib   media       opt  root  sbin  sys  usr

成了。自己编的主线内核 7.2.2、自己 debootstrap 的 Debian、自己建的用户登录进去,NetworkManager 和 sshd 都正常拉起。

有个无害报错,忽略即可:

panfrost 1800000.gpu: deferred probe timeout, ignoring dependency
panfrost 1800000.gpu: probe with driver panfrost failed with error -110

Mali GPU 驱动没探测到,不用图形界面无所谓。


10. 接着搞 USB Gadget(进行中)

系统活了,能看 dmesg 了,正是配 gadget 的好时候。

l@opizero2w:/$ ls /sys/class/udc/
musb-hdrc.2.auto

l@opizero2w:/$ ls /sys/kernel/config/
pci_ep
l@opizero2w:/$ mount | grep configfs
configfs on /sys/kernel/config type configfs (rw,nosuid,nodev,noexec,relatime)

configfs 挂着,但 usb_gadget 目录还没出现——因为内核里 CONFIG_USB_CONFIGFS=m,得先 sudo modprobe libcomposite。

注意 UDC 是 musb-hdrc.**2**.auto,而我那块 Armbian 板子上是 .4. ——编号会变,脚本里别硬编码,用 udc=$(ls /sys/class/udc | head -n1) 自动探测。

剩下的活:

  • configfs 拼 gadget:设 VID/PID → 建 NCM function(比 ECM 新、性能好,Linux/Mac 完美支持;Windows 才需要 RNDIS)→ 固定两端 MAC(这样电脑侧网卡名稳定)→ 绑 UDC

  • NetworkManager 给 usb0 配静态 IP —— 注意我这套用的是 NM 不是 systemd-networkd,网上那些 .network 文件方案抄了不生效

  • 做成 systemd 服务开机自启

  • IP 规划:板子 192.168.137.2/24,电脑侧 192.168.137.1/24

还有俩口子要试:Zero 2W 两个 USB-C,得插上看 dmesg 有反应的那个才是 device 模式的口。

搞定了再开一贴。


后记:这趟最有用的几条

  1. 不看日志看产物。 编译完直接 ls -lh 查文件在不在、大小对不对,比翻几千行编译输出可靠。

  2. 别盲改脚本烧卡,停进 U-Boot 命令行手动敲。 printenv + load + md 三条命令十分钟定位到了我烧三轮卡都没找出来的问题。

  3. 校验一定要 umount 再 mount。 不然读的是页缓存,坏块永远暴露不出来。

  4. 反复出现的 I/O 错误、"只能开机一次"这类症状,先怀疑地基。 卡不可靠的话,内核、rootfs、引导脚本做得再对也白搭——数据写进去就变样了。我在软件层面绕了三圈才想到这一层。



专家
2026-09-02 19:56:52     打赏
2楼

大牛!这种系统,外设(SPI、I2C等)的底层驱动也要自己编写吗?


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]