为了让不在OpenWrt项目支持列表中的ARM机器快速使用OpenWrt,可以用替换rootfs的简易方法进行移植。
首先,确保你的ARM盒子有能正常使用的Linux系统,并且最好有良好的社区支持,可以去armbian寻找有开源支持的开发板。
以下SoC的开源支持比较良好,多数情况下可以用上主线内核:
- Rockchip
- RK3328/RK3368/RK3399 系列
- amlogic
- s905/s912/s922 系列
- sunxi
- h2/h3/h5 系列
- marvell
- armada a3700 系列
移植OpenWrt的rootfs过程中,需要特别注意的是必须保证原有系统内与内核有关的东西(包括内核模块)不能有任何丢失。多数固件的Linux内核镜像、dtb和uboot的部分变量都是直接存放到rootfs里面的,在替换的时候一定要把这些文件完整保留。
注意:替换rootfs后,机器实际使用的内核与openwrt中由opkg管理的内核(包含内核模块)没有任何关系,因此如果要增删内核模块的话不能使用opkg进行管理。
具体流程:
准备好Linux环境,可以用虚拟机或实体机,不能用wsl
-
下载armvirt的通用rootfs,以OpenWrt 18.06.4为例
- 32位arm,适合于Cortex A7/A9/A15等:
https://downloads.openwrt.org/releases/18.06.4/targets/armvirt/32/openwrt-18.06.4-armvirt-32-default-rootfs.tar.gz - 64位arm,适合于Cortex A53/A72等:
https://downloads.openwrt.org/releases/18.06.4/targets/armvirt/64/openwrt-18.06.4-armvirt-64-default-rootfs.tar.gz
- 32位arm,适合于Cortex A7/A9/A15等:
-
判断固件的分区类型
- 对于amlogic的固件,一般有2个分区:第一分区为FAT32分区,用于存放内核镜像、dtb、uboot变量和脚本;第二分区为ext4分区,用作真正的rootfs。替换的时候必须保留FAT32分区的所有内容以及ext4分区内的
/lib/modules
和/lib/firmware
- 对于rockchip或sunxi的sd卡固件,一般只有1个ext4分区作为rootfs,此分区内使用
/boot
文件夹用于存放内核、dtb、uboot变量和脚本。在替换的时候,必须保留此分区内的/lib/modules
、/lib/firmware
以及/boot
。此类固件的bootloader存放在ext4分区前未分配的空间中。
- 对于amlogic的固件,一般有2个分区:第一分区为FAT32分区,用于存放内核镜像、dtb、uboot变量和脚本;第二分区为ext4分区,用作真正的rootfs。替换的时候必须保留FAT32分区的所有内容以及ext4分区内的
-
准备好你要修改的固件,注意修改的固件的rootfs分区必须是可写入的文件系统(如ext4),否则不能操作。
- 对于从SD卡启动的机器,将固件用etcher写入SD卡,然后在你的Linux环境下挂载好SD卡,继续下一步操作。
- 对于提供emmc刷机固件的机器,需要用loop挂载镜像,具体操作如下:
# 创建挂载目录
mkdir -p /mnt/rootfs
# 查看固件的分区表
sfdisk -J /path/to/firmware
#
# 以amlogic的双分区(FAT32+EXT4)固件为例:
# {
# "partitiontable": {
# "label":"dos",
# "id":"0x1028b956",
# "device":"/path/to/firmware",
# "unit":"sectors",
# "partitions": [
# {"node":"/path/to/firmware1", "start":8192, "size":262144, "type":"e"},
# {"node":"/path/to/firmware2", "start":270336, "size":30694112, "type":"83"}
# ]
# }
# }
# 可以看到rootfs为第二分区,并且偏移量为270336 blocks,所以偏移的字节数为270336*512
# 带偏移量挂载
sudo mount -o loop,offset=$((270336*512)) /path/to/firmware /mnt/rootfs
- 处理
/lib/modules
一般来说Linux发行版的/lib/modules
目录结构都是这样的:
/lib/modules/<内核版本号>/
├── kernel
│ ├── arch
│ ├── crypto
│ ├── drivers
│ ├── fs
│ ├── lib
│ ├── mm
│ ├── net
│ ├── security
│ ├── sound
│ └── virt
├── modules.alias
├── modules.alias.bin
├── modules.builtin
├── modules.builtin.bin
├── modules.dep
├── modules.dep.bin
├── modules.devname
├── modules.order
├── modules.softdep
├── modules.symbols
└── modules.symbols.bin
而OpenWrt的/lib/modules/<内核版本号>
下面直接存放kernel
目录下的所有模块,所以需要我们手工移动一下,具体操作如下:
# root挂载的目录一般需要root权限,可以用sudo或者切换成root用户操作
# 进入rootfs的挂载点
cd /path/to/rootfs
# 进入内核模块目录
cd ./lib/modules/<内核版本号>/
# 删除当前目录下的所有文件,但不删除kernel目录
sudo rm -f * 2>/dev/null
# 找出kernel目录下面的所有ko并移动到当前目录下
sudo mv $(find kernel -type f) .
# 删除空的kernel文件夹
sudo rm -r kernel
- 备份文件并解压OpenWrt的rootfs
如果该镜像的内核镜像、dtb或uboot脚本等文件在rootfs里面的话,需要先备份出来。对于不同板子和不同固件,这一步的操作都不同,下面以armbian为例:
# 创建一个临时目录用于存放备份文件
mkdir -p /tmp/backup
# 进入rootfs的挂载点
cd /path/to/rootfs
# 对于把内核、dtb或uboot变量放在/boot目录的固件,例如sunxi和rockchip,需要将整个/boot目录备份出来
cp -ra ./boot /tmp/backup/boot
# 备份/lib/modules和/lib/firmware
cp -ra ./lib/modules /tmp/backup/
cp -ra ./lib/firmware /tmp/backup/
# 备份后删除当前rootfs下的所有文件
sudo rm -rf *
# 将OpenWrt的rootfs解压出来
sudo tar -xvf /path/to/openwrt/rootfs.tar.gz
# 删除OpenWrt rootfs里自带的/boot目录
sudo rm -rf ./boot
# 恢复刚才备份的目录
sudo mv -f /tmp/backup/boot .
sudo mv -f /tmp/backup/modules/<内核版本号> ./lib/modules
sudo mv -f /tmp/backup/firmware ./lib/firmware
- 修改rootfs
- 启用串口的getty:对于串口设备名是
ttyS0
的内核,/etc/inittab
里已经包含,所以无需修改,而其它串口设备名就需要手动添加,具体如下:
# 进入rootfs的挂载点
cd /path/to/rootfs
# 对于amlogic的内核,串口设备名为ttyAML0
echo "ttyAML0::askfirst:/usr/libexec/login.sh" |sudo tee -a ./etc/inittab
# 对于rockchip的bsp内核,串口设备名为ttyFIQ0
echo "ttyFIQ0::askfirst:/usr/libexec/login.sh" |sudo tee -a ./etc/inittab
# 对于marvell armada的bsp内核,串口设备名为ttyMV0
echo "ttyMV0::askfirst:/usr/libexec/login.sh" |sudo tee -a ./etc/inittab
- rootfs修改完成,卸载文件系统
sync
cd / && sudo umount /path/to/rootfs
- 启动修改后的固件,进入OpenWrt之后检查以下命令能否正常工作:
iptables -L
ip6tables -L
ip route
如果你的内核版本大于4.18,且iptables抛出以下错误:
root@OpenWrt:~# iptables -L
iptables v1.6.2: can't initialize iptables table `filter': No child process
那么说明原内核启用了bpfilter,这可能需要重新编译整个内核,请参考这篇文章:https://www.jianshu.com/p/48e2f3e6caeb