OrangePi-3B 系列笔记:
- OrangePi-3B 折腾笔记 —— 认识开发板
- OrangePi-3B 折腾笔记 —— 准备构建环境
- OrangePi-3B 折腾笔记 —— 构建 U-Boot
- OrangePi-3B 折腾笔记 —— 构建 Kernel
- OrangePi-3B 折腾笔记 —— 点亮 LCD 屏幕
- OrangePi-3B 折腾笔记 —— 移植 WiFi & BT 驱动
- OrangePi-3B 折腾笔记 —— 构建文件系统镜像
温馨提示:
- OrangePi-3B 硬件版本 V1.1.1 的板子出现过有线网卡“无故”失灵的故障, 所以在进行操作前请确认开发板工作良好(修复这个问题需要更换 RK3566 芯片).
https://www.bilibili.com/read/cv33224126- 香橙派目前已经推出了硬件版本为 V2.1 的 OrangePi-3B, 有线网卡供应商更换为 RTL. 本系列文章没有针对 V2.1 的板子做过适配, 请根据实际情况操作(同时强烈谴责此种不负责任的行为, 如考虑购买此型号板子商用, 建议换个型号避坑).
- 本系列文章最后更新时间: 2024年11月02日.
编译环境
- 主板: OrangePi-3b_V1.1.1
- 芯片: RK3566
- 环境: Debian:12_x86_64(Docker)
构建方式
一般嵌入式系统构建根文件系统会使用 buildroot
这个项目,构建的根文件系统足够小。但 buildroot
构建时严重依赖外网, 导致编译过程很慢。
为了使用有包管理器的操作系统,可以基于 Debian
或 Fedora
来构建根文件系统。它们都提供了专门的工具来支持这个操作:
- debootstrap:
debootstrap
是debian/ubuntu
下的一个工具,用来构建一套基本的操作系统(根文件系统)。生成的目录符合 Linux 文件系统标准 (FHS),即包含了 /boot、/etc、/bin、/usr 等等目录,它比发行版本的Linux
体积小很多,可以说是 “基本的操作系统”。 - febootstrap:
febootstrap
是fedora/centos
下的一个工具,功能类似debootstrap
。
前几年从 CentOS 7
全面切换到 Debian
操作系统,为了方便这里也选择基于 Debian
来构建根文件系统。
构建可以分为两部分,分别是构建 rootfs
文件系统和构建 initramfs
文件系统。前者是 Linux
发行版,后者给 Kernel
作为 ramfs
使用。
个人理解
initramfs
的主要作用是方便在挂载真正的根分区时执行一些用户空间的操作,比如磁盘检查等操作,还是很有必要存在的。
为了能够通过 U-Boot
的引导配置文件启动系统,这里没有使用之前构建 U-Boot
的分区方案,而是只保留一个分区作为根分区。
U-Boot
加载extlinux.conf
引导配置文件默认从存储设备的第一个分区加载。
构建
克隆代码
cd ~/projects/
git clone https://github.com/armbian/firmware.git -b master armbian-firmware
- 驱动
UWE5622
需要对应的固件,同样也是用Armbian
仓库中现成的二进制文件;
启动容器
docker run -ti --rm --privileged -v ~/projects/:/projects/ build-linux bash
- 关于镜像
build-linux
的内容请参考之前的笔记,其中已经安装构建所需要的软件包。
以下是相关软件包的介绍:
- debootstrap
用于构建Linux
发行版的程序 - binfmt-support
用于开启内异构程序模拟器运行的支持 - qemu-user-static
用于运行异构程序的模拟器,安装过程会自动开启异构程序的支持;也可以手动开启(比如使用容器时需要手动开启):# 开启 sudo update-binfmts --package=qemu-user-static --enable # 检查 sudo ls -lh /proc/sys/fs/binfmt_misc/ # 关闭 sudo update-binfmts --disable
注意:
update-binfmts
命令是内核控制指令,行为会影响宿主机,所以容器环境一般只需要开启一次; - u-boot-tools
用于制作U-Boot
镜像文件的工具集(只是记录,实际得使用虚拟环境中的u-boot-tools
)。 - initramfs-tools
用于创建initramfs
镜像文件的工具集(只是记录,实际得使用虚拟环境中的initramfs-tools
)。 - android-sdk-libsparse-utils
用于创建Android
的Sparse
镜像文件的工具集。 - e2fsprogs
Ext2/3/4
文件系统工具(Ext2Filesystems Utilities
),它包含了诸如创建、修复、配置、调试ext
文件系统等的标准工具。 - file/fdisk/gdisk/parted
一些文件和文件系统的辅助工具。
构建基础系统
cd /projects/buildfs/
# 下载基础系统和包
sudo debootstrap --verbose \
--foreign \
--arch=arm64 \
bookworm \
rootfs \
https://mirrors.tuna.tsinghua.edu.cn/debian/
# 使用 chroot 切换到文件系统进行第二阶段部署(部署基础系统和包)
sudo chroot rootfs /debootstrap/debootstrap --second-stage --verbose
# 清理临时文件
sudo rm -rf rootfs/var/cache/*
sudo rm -rf rootfs/var/log/*
sudo rm -rf rootfs/var/lib/apt/lists/*
- 用于下载基础系统和包的
debootstrap
是x64
架构程序;- 用于第二阶段部署的
debootstrap
是arm64
架构的程序;- 使用
chroot
命令切换到rootfs
文件系统,搭配qemu
来模拟出arm64
的运行环境执行debootstrap
完成第二阶段部署;当然也可以用真正的arm64
环境来执行第二阶段部署;- 根据网上流传的教程,执行第二阶段部署前都需要挂载宿主机
/proc/
、/sys/
、/dev/
和拷贝qemu-*-static
可执行文件到目标目录结构,但是实际测试发现好像也不需要(可能是由于我是在docker
容器的原因);
部署工具 debootstrap
的参数含义如下:
- –verbose
显示构建细节。 - –foreign
在与主机架构不相同时需要指定此参数,仅做初始化的下载。如果已经部署了模拟环境,可以不设置这个参数使得一并完成第二阶段部署(代替 –second-stage 参数)。 - –variant=X
使用引导脚本的变体X(目前支持的变体: buildd, fakechroot, minbase)。 - –arch
指定制作的文件系统的架构。 - –include=A,B,C
将指定的名称添加到基包列表中。 - –exclude=A,B,C
从列表中删除指定的包。 - –second-stage
第二阶段部署。一般制作异构文件系统时,宿主机无法执行目标架构的可执行文件。所有先下载包,然后切换到目标架构运行环境执行部署。 - bookworm
bookworm
是Debian 12
的发行版本名称。 - rootfs
存放文件系统的文件夹,能够自动创建。 - https://mirrors.tuna.tsinghua.edu.cn/debian/
清华Debian
镜像服务器地址。
自定义系统
现在已经通过 debootstrap
初始化了 Debian
基础系统。接下来安装常用软件和自定义系统配置:
cd /projects/buildfs/
# 挂载本地设备
sudo mount -t proc -o nosuid,noexec,nodev proc rootfs/proc
sudo mount -t sysfs -o nosuid,noexec,nodev sysfs rootfs/sys
sudo mount -t devtmpfs -o mode=0755,nosuid devtmpfs rootfs/dev
sudo mount -t devpts -o gid=5,mode=620 devpts rootfs/dev/pts
# 进入虚拟 arm64 环境
sudo chroot rootfs /bin/bash
# 安装软件包
apt update
apt install -y sudo ssh locales rsyslog \
net-tools nfs-common wget curl ntp vim bash-completion command-not-found \
network-manager rfkill wireless-tools bluetooth bluez-tools \
u-boot-tools initramfs-tools \
file fdisk gdisk parted \
lm-sensors
# 构建 command-not-found 数据库
apt update
# 设置时区
dpkg-reconfigure tzdata
# 设置语言
dpkg-reconfigure locales
# 设置主机名
HOSTNAME=OrangePi-3b
echo $HOSTNAME > /etc/hostname
sed -i '/localhost/s/$/ '"$HOSTNAME"'/g' /etc/hosts
# 添加用户
useradd --create-home --groups users,sudo --shell /bin/bash user
echo 'user:1111' | chpasswd
# 设置别名
cat > /etc/profile.d/aliases.sh <<\EOF
alias ll='ls -lh -F --color=auto --time-style=long-iso'
alias la='ls -lhA -F --color=auto --time-style=long-iso'
alias lt='ls -lht -F --color=auto --time-style=long-iso'
alias lat='ls -lhAt -F --color=auto --time-style=long-iso'
EOF
# 退出虚拟 arm64 环境
apt clean
exit
# 卸载本地设备
sudo umount rootfs/proc rootfs/sys rootfs/dev/pts rootfs/dev
# 清理临时文件
sudo rm -rf rootfs/var/cache/*
sudo rm -rf rootfs/var/log/*
sudo rm -rf rootfs/var/lib/apt/lists/*
sudo rm -rf rootfs/root/.bash_history
- 使用
chroot
命令搭配qemu
的功能切换到rootfs
文件系统,然后可以执行一些手动的配置;
拷贝 Kernel
& Firmware
到文件系统
cd /projects/buildfs/
# 自动获取内核版本
utsrelease=$(cat ../kernel/out/include/generated/utsrelease.h)
kernel_version=$(echo ${utsrelease:21:-1})
echo ${kernel_version}
# 拷贝 kernel 产物(config/kernel/dtb)
sudo rm -rf rootfs/boot/*
sudo cp -a ../kernel/out/.config rootfs/boot/config-${kernel_version}
sudo cp -a ../kernel/out/arch/arm64/boot/Image.gz rootfs/boot/Image.gz
sudo mkdir -p rootfs/boot/dts/rockchip/overlay/
sudo cp -a ../kernel/out/arch/arm64/boot/dts/rockchip/rk3566*.dtb rootfs/boot/dts/rockchip/
sudo cp -a ../kernel/out/arch/arm64/boot/dts/rockchip/rk35*.dtbo rootfs/boot/dts/rockchip/overlay/
sudo chown -R root:root rootfs/boot/
# 拷贝 kernel 产物(modules)
sudo rm -rf rootfs/lib/modules/
sudo cp -a ../kernel/out/lib/modules/ rootfs/lib/
sudo chown -R root:root rootfs/lib/modules/
# 注册驱动开机加载(UWE5622)
sudo cat > uwe5622.conf <<\EOF
sprdbt_tty
sprdwl_ng
EOF
sudo chown root:root uwe5622.conf
sudo mv uwe5622.conf rootfs/etc/modules-load.d/
# 拷贝固件(Rockchip)
sudo cp -rf ../armbian-firmware/rockchip/ rootfs/lib/firmware/
# 拷贝固件(UWE5622)
sudo cp -rf ../armbian-firmware/uwe5622/ rootfs/lib/firmware/
sudo cp -rf ../armbian-firmware/bt_configure_*.ini rootfs/lib/firmware/
sudo ln -s uwe5622/wcnmodem.bin rootfs/lib/firmware/wcnmodem.bin
sudo ln -s uwe5622/wifi_2355b001_1ant.ini rootfs/lib/firmware/wifi_2355b001_1ant.ini
# 拷贝定制软件包(UWE5622)
sudo cp -rf ../armbian-build/packages/bsp/rk3399/hciattach_opi rootfs/usr/bin/hciattach_opi
sudo chmod 755 rootfs/usr/bin/hciattach_opi
sudo cp -rf ../armbian-build/packages/bsp/rk3399/sprd-bluetooth.service rootfs/lib/systemd/system/
sudo ln -s /lib/systemd/system/bluetooth.service rootfs/etc/systemd/system/multi-user.target.wants/sprd-bluetooth.service
- 注意其中
${kernel_version}
是Kernel
构建的版本号,Kernel
和modules
需要一致;(可通过在Kernel
目录执行make O=out kernelrelease
查看)- 缺少
modules
可能会导致一些设备无法正常驱动,比如网卡;
创建 U-Boot
引导文件
U-Boot
支持 boot.scr
和 extlinux.conf
两种启动方式的引导文件,两种任选其一即可。
方式一:使用 boot.scr
作为启动脚本
cd /projects/buildfs/
# 生成脚本配置
sudo cat > boot.env <<\EOF
earlycon=on
loglevel=7
fdtfile-overlay=
fdtfile=rockchip/rk3566-orangepi-3b-custom.dtb
EOF
sudo chown root:root boot.env
sudo mv boot.env rootfs/boot/
# 生成启动脚本
sudo cat > boot.cmd <<\EOF
setenv load_addr_r "0x6000000"
setenv kernel_addr_r "0x4000000"
setenv ramdisk_addr_r "0x2000000"
setenv fdt_addr_r "0x1000000"
# default values
setenv earlycon "off"
setenv console "both"
setenv bootlogo "false"
setenv prefix "/boot/"
setenv fdtfile "rockchip/rk3566-orangepi-3b.dtb"
setenv rootdev "/dev/mmcblk0p1"
setenv rootfstype "ext4"
setenv rootfsmode "rw"
setenv loglevel "7"
echo "Boot script loaded from ${devtype} ${devnum}"
if test -e ${devtype} ${devnum} ${prefix}boot.env; then
load ${devtype} ${devnum} ${load_addr_r} ${prefix}boot.env
env import -t ${load_addr_r} ${filesize}
fi
if test "${earlycon}" = "on"; then setenv consoleargs "${consoleargs} earlycon"; fi
if test "${console}" = "display" || test "${console}" = "both"; then setenv consoleargs "console=tty1"; fi
if test "${console}" = "serial" || test "${console}" = "both"; then setenv consoleargs "${consoleargs} console=ttyS2,1500000n8"; fi
if test "${bootlogo}" = "true"; then
setenv consoleargs "${consoleargs} splash plymouth.ignore-serial-consoles"
else
setenv consoleargs "${consoleargs} splash=verbose"
fi
setenv bootargs "${consoleargs} consoleblank=0 loglevel=${loglevel} root=${rootdev} rootfstype=${rootfstype} rootwait ${rootfsmode} ${extraargs}"
load ${devtype} ${devnum} ${kernel_addr_r} ${prefix}Image.gz
load ${devtype} ${devnum} ${ramdisk_addr_r} ${prefix}initramfs.img
load ${devtype} ${devnum} ${fdt_addr_r} ${prefix}dts/${fdtfile}
fdt addr ${fdt_addr_r}
fdt resize 65536
for file in ${fdtfile-overlay}; do
if load ${devtype} ${devnum} ${load_addr} ${prefix}dts/${file}; then
echo "Applying DT overlay ${file}"
fdt apply ${load_addr} || setenv overlay_error "true"
fi
done
if test "${overlay_error}" = "true"; then
echo "Error applying DT overlays, restoring original DT"
load ${devtype} ${devnum} ${fdt_addr_r} ${prefix}dts/${fdtfile}
fi
booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}
EOF
sudo chown root:root boot.cmd
sudo mv boot.cmd rootfs/boot/
sudo mkimage -C none -A arm -T script -d rootfs/boot/boot.cmd rootfs/boot/boot.scr
- 这个启动脚本是仿照的
Armbian
的启动脚本;
方式二:使用 extlinux.conf
作为启动配置
cd /projects/buildfs/
# 生成启动配置
sudo cat > extlinux.conf <<\EOF
# u-boot/boot/pxe_utils.c
# static const struct token keywords[]
menu title ORANGEPI-3B
default Debian
timeout 10
label Debian
kernel /boot/Image.gz
initrd /boot/initramfs.img
devicetree /boot/dts/rockchip/rk3566-orangepi-3b-custom.dtb
# devicetree-overlay
append earlycon console=tty1 console=ttyS2,1500000n8 consoleblank=0 loglevel=7 root=/dev/mmcblk0p1 rootfstype=ext4 rootwait rw
EOF
sudo chown root:root extlinux.conf
sudo mkdir -p rootfs/boot/extlinux/
sudo mv extlinux.conf rootfs/boot/extlinux/
timeout
单位是0.1
秒;
制作 initramfs
镜像
使用 Debian
系操作系统的 mkinitramfs
命令来实现:
cd /projects/buildfs/
# 进入虚拟 arm64 环境
sudo chroot rootfs /bin/bash
# 自动获取内核版本
config_name=$(ls /boot/config-*)
kernel_version=$(echo ${config_name:13})
echo ${kernel_version}
# 新增自定义程序配置
cat > /etc/initramfs-tools/hooks/fsck <<\EOF
#!/bin/sh
. /usr/share/initramfs-tools/scripts/functions
. /usr/share/initramfs-tools/hook-functions
copy_exec /sbin/e2fsck
copy_exec /sbin/fsck
copy_exec /sbin/fsck.cramfs
copy_exec /sbin/fsck.ext2
copy_exec /sbin/fsck.ext3
copy_exec /sbin/fsck.ext4
copy_exec /sbin/logsave
EOF
chmod 755 /etc/initramfs-tools/hooks/*
chown root:root /etc/initramfs-tools/hooks/*
# 新增自定义模块配置
cat > /etc/initramfs-tools/modules <<\EOF
# List of modules that you want to include in your initramfs.
# They will be loaded at boot time in the order below.
#
# Syntax: module_name [args ...]
#
# You must run update-initramfs(8) to effect this change.
#
# Examples:
#
# raid1
# sd_mod
blocklayoutdriver
EOF
# 打包 initramfs 文件系统
mkinitramfs -c gzip -o /boot/initramfs.gz ${kernel_version}
# 构建镜像文件
mkimage -A arm64 -O linux -T ramdisk -C none -a 0 -e 0 -n initramfs -d /boot/initramfs.gz /boot/initramfs.img
# 退出虚拟 arm64 环境
exit
# 清理临时文件
sudo rm -rf rootfs/boot/initramfs.gz
sudo rm -rf rootfs/root/.bash_history
- 同样使用
chroot
命令搭配qemu
的功能来切换到initramfs
文件系统目录结构,然后使用initramfs-tools
的工具来制作initramfs
文件系统镜像;- 注意其中
${kernel_version}
是Kernel
构建的版本号,需要和Kernel
的modules
对应;(可通过在Kernel
目录执行make O=out kernelrelease
查看)
制作 rootfs
镜像文件
cd /projects/buildfs/
# 创建 rootfs 文件系统镜像
dd if=/dev/zero of=rootfs.img bs=1M count=0 seek=2048
sudo mkfs.ext4 -F rootfs.img
# 临时挂载镜像文件并填充内容
mkdir -p image
sudo mount rootfs.img image
sudo cp -a rootfs/* image/
sudo umount image
rm -rf image
# 修改镜像文件大小(可选,优化之后分区也随之变小)
sudo e2fsck -fy rootfs.img
sudo resize2fs -f rootfs.img 800M
# 缩小镜像至最小(可选,优化之后分区也随之变小)
sudo e2fsck -fy rootfs.img
sudo resize2fs -M rootfs.img
主线 U-Boot
内置的 fastboot
支持刷入 Android
的 Sparse
镜像文件,这种镜像文件的尺寸会更小很多。可以使用 img2simg
将上面制作的 rootfs.img
转换成 Sparse
镜像:
img2simg rootfs.img rootfs-sparse.img
Android
的Sparse
镜像文件不能使用RKDevTool
直接刷入;也不能直接挂载,需要使用simg2img
还原之后才能挂载;
当 rootfs.img
刷入开发板后,可能文件系统的大小和分区表是不一样的,可以在进入系统之后使用 resize2fs
命令对齐分区到分区表的大小:
sudo resize2fs /dev/mmcblk0p1
构建产物
buildfs/rootfs/boot/initramfs.img
initramfs
文件系统镜像,可通过U-Boot
提供给Kernel
使用;buildfs/rootfs.img
rootfs
文件系统镜像,烧录到磁盘rootfs
分区使用;buildfs/rootfs-sparse.img
rootfs
文件系统镜像的Sparse
版本,使用U-Boot
的fastboot
模块刷入磁盘rootfs
分区使用;
调试
如果频繁变动 RootFS
,可以通过 Kernel
挂载 NFS
来快速验证。
使用 boot.scr
作为启动脚本
编辑 /boot/boot.env
文件添加相关配置即可:
earlycon=on
loglevel=7
fdtfile-overlay=
fdtfile=rockchip/rk3566-orangepi-3b-custom.dtb
rootdev=/dev/nfs
extraargs=nfsroot=192.168.8.13:/home/liux/projects/buildfs/rootfs/,vers=4 ip=dhcp
rootfstype=ext4
使用 extlinux.conf
作为启动配置
编辑 /boot/extlinux/extlinux.conf
文件修改相关配置即可:
# u-boot/boot/pxe_utils.c
# static const struct token keywords[]
menu title ORANGEPI-3B
default Debian
timeout 10
label Debian
kernel /boot/Image.gz
initrd /boot/initramfs.img
devicetree /boot/dts/rockchip/rk3566-orangepi-3b-custom.dtb
# devicetree-overlay
append earlycon console=tty1 console=ttyS2,1500000n8 consoleblank=0 loglevel=7 root=/dev/nfs nfsroot=192.168.8.13:/home/liux/projects/buildfs/rootfs/,vers=4 ip=dhcp rootfstype=ext4 rootwait rw
问题记录
Kernel
启动rootfs
过程中串口加载超时:
解决办法:[ **] Job dev-ttyS2.device/start running (30s / 1min 30s) [ TIME ] Timed out waiting for device dev-ttyS2.device - /dev/ttyS2. [DEPEND] Dependency failed for seri…rvice - Serial Getty on ttyS2.
rootfs
中预置udev
软件包