摘要:
有时候我们不想使用厂家提供的开发板Linux系统镜像,而是想要一个基于某款芯片的纯净开发环境进行某些驱动测试,这个时候就需要手动构建自己的板上系统。我们希望这个板上系统只需要有最基本的功能即可,以便验证自己的开发内容是否正常。
原理:
想要构建一个板上Linux系统,我们大致需要以下几大部件:bootloader(u-boot)、Linux的内核和设备树、文件系统。
启动的流程为:板子上电以后芯片自动执行预设好的地址上的程序,即bootloader,随后bootloader将压缩的Linux内核搬运到内存进行解压展开,展开内核以后板子就拥有了系统运行的逻辑框架以及必要的硬件驱动支持,拥有读写文件系统的能力,而文件系统则提供了系统的上层软件支持,所有的软件都会以文件形式存在于文件系统。
---------------------------------------------------------------------------------------------------------------------------------
接下来演示具体的操作,如果想要和我使用相同的板子进行实操,可以看下图然后下载对应的资料:百度网盘 请输入提取码 码: k6re
如果不想和我使用相同板子,那么购买的板子必须要支持串口调试,否则非常麻烦,且最好有一个RJ45网口。
注意:值得注意的是即使是按照和我一模一样的操作,也是有可能出现各种奇怪的问题的。Uboot、内核以及文件系统这三个东西都是经我反复测试没问题以后放上去的,所以优先怀疑是开发环境差软件包或是环境变量没配置好导致的报错。
U-BOOT
u-boot是为了让使用更加统一而推行的一款通用boot loader,支持多种平台且具备调试工具,而不需要进行高强度的定制就可以使用,现在使用uboot引导系统启动已经是一种主流的规范做法了。Uboot的任务在于将内核、设备树搬运到指定的位置然后告诉板子如何启动。
操作
如果有厂家提供的uboot,则优先使用厂家的uboot,他们的完成度无疑是更好的。如果没有再去下载主线uboot 来使用。
(1)下载uboot
首先可以从这里下载uboot:Obtaining the source — Das U-Boot unknown version documentation
关于如何查看自己的板子是否得到支持,可以注意一下uboot根目录下的configs文件夹,这里存放了关于具体板子的配置文件,指明了这个板子需要哪些驱动以及对应的设备树是哪个。
假设没有对应的配置文件,就使用相同芯片的板子的配置文件,至少二者在启动地址上是相同的,还是有能使用的可能性。
(2)下载ATF
下载完uboot我们还需要它的前置组件ATF,可以理解为ARM为安全考虑制定的一种规范,没有它就不能编出一个能用的uboot。
ATF的下载:TrustedFirmware-A/trusted-firmware-a: Read-only mirror for Trusted Firmware A (github.com)
(3)下载交叉编译工具链
因为我们最后编译出来的内核以及uboot是要放到ARM平台使用的,所以我们还需要将其从x86平台交叉编译到ARM平台才行。
同样,可以从我提供的链接进行下载,里面涵盖了各种系统位数、各种发行版本。
(4)编译ATF组件及u-boot
1)首先得配置一下交叉编译工具链
假设是使用我提供的交叉工具链包,进入toochain的文件夹能看到这些内容
因为我编译的是64位的系统,所以我需要使用带aarch后缀的编译工具(只写了arm的是默认32位系统使用),选一个进入(正常情况下如果位数选对了那选哪个其实都没什么区别,如果编译太老的内核而编译工具下的动态链接库太新用不了,换一个就行),然后复制它的路径。
进入配置编译,输入:
vim /etc/bash.bashrc
在最后加上如下PATH和LD_LIBRARY两个变量,将刚才复制的路径粘贴进去,然后末尾分别加上/bin和/lib,如图即可,保存退出
退出后更新路径并查看编译器版本:
source /etc/bash.bashrc
aarch64-linux-gnu-gcc --version
能看到版本号就算配置成功了
注意:这里编译器名称不是完全一致的,要和上面编译器路径下的bin目录里的可执行程序名称一致才行,比如我刚刚输入的是这个
2)配置好工具以后就可以进行编译了
先编译ATF,平台选择可以参考这里进行
挑选好平台以后回到trusted-firmware-a 文件的根目录,输入:
make CROSS_COMPILE=aarch64-linux-gnu PLAT=平台名称
注意:编译器的这个变量没有-gcc的后缀
结束编译后到这个路径看看 build/你的平台/debug
然后编译uboot,将刚才编译出来的bl31.bin复制到uboot的根目录,比如这样
同样,去寻找你的平台配置文件,其中Orangepi-zero3的配置文件为orangepi_zero3_defconfig
假设没有,那就使用相同芯片的其他板子的配置文件也行,下一步的时候不选无关驱动即可。
然后配置编译文件
make CROSS_COMPILE=aarch64-linux-gnu orangepi_zero3_defconfig
make CROSS_COMPILE=aarch64-linux-gnu menuconfig
然后就会出现这个界面,操作方法如下,可以选配板子的uboot驱动设置,使用其他厂家的配置文件需要进行一定的裁剪,但是不裁也行,裁剪了反而容易出问题。
我这里使用的是现成的文件,所以不配置了,直接开始编译:
make CROSS_COMPILE=aarch64-linux-gnu-
编译完成以后长这样
这个带”-with-spl”的就是我们需要的u-boot,它整合了完整的启动递交流程到一个文件,就不需要我们配置琐碎的各种文件了。
内核
操作
(1)下载内核
我这里以主线内核演示,我选择6.10.11这个(但是注意如果是需要大量使用,内核的版本选择不应太新,也不应太旧,适中就好,免得出现不兼容问题)
(2)编译内核
解压出来以后长这样,输入下面指令
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
这里的配置除了厂商特供的内核版本,配置文件统一都是只有defconfig,需要下一步手动剪裁。
还是和编译uboot类似的操作,进行menuconfig,首先进行一些粗剪裁,进入平台选择(光标指到这个位置按回车)
可以看到默认配置里兼容了非常多的平台以及驱动,我们先按空格取消其他显然不需要的,比如我使用的全志H618,就是只保留Allwinner这个就可以了
最后这样
当然,如果和我使用相同的板子,还需要加一个驱动以兼容以太网使用,位置在这里
Tips:如果有想要加入/删除的驱动,可以按“/”进行搜索
我就只进行这些操作,保存退出(虽然支持驱动越多编译就会越慢,有时编译甚至会久至数小时,但第一次起内核不建议进行过多的剪裁,在不清楚要不要这个驱动的时候就选择要,避免反复出错进而反复编译坐牢的情况。调通过能正常启动以后再回过头来慢慢裁剪即可)
回车就开始编译了,很久,等吧
编译完成后输入
ls arch/arm64/boot/
看见内核镜像就说明编译成功了
接下来还需要设备树文件,输入
ls arch/arm64/boot/dts/
对应厂家应该有对应的.dtb文件(设备树的二进制文件);如果没有,则单独编译
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs
可启动SD卡制作
操作
(1)写入uboot
1)清理
首先介绍一些dd指令:
if | 输入 |
of | 输出 |
seek | 开始的块 |
bs | 一个块的大小 |
count | 写多少个块 |
其次是识别自己插入的sd卡到底是哪张,
lsblk
可以查看所有磁盘内容。我使用的sd卡为64g,那么折合就应该是60g左右,应该是sdb
先擦除第一M的区域,因为在sd启动卡上存在一个默认规则就是第一M的区域应该放sd卡的分区表、boot loader以及其他引导标识等内容,所以我们应先清理启动区域以便能够放入我们自己的启动文件
注意:这个清理速度不应该过快,十几兆的写入比较正常,几百兆肯定有问题,USB2.0转接的速率不可能这么快
清除成功以后的sd卡不应该有任何的分区,即
2)重分区
完事以后别忘了按 "w+回车" 写入,写入后会自动退出
分配完成后可以再次lsblk看一眼
然后规定分区类型,这么规定类型有以下几个理由:
首先,uboot变量只保存在fat类型的区域,所以紧跟1M地址后的分区是fat
其次,后期需要挂载到主机对文件系统进行其他配置,而读写的时候需要类型和架构均匹配;同时fat最大文件大小为4G,显然是不够大的,所以最后一个存放文件的分区为ext4
输入下列指令进行分区配置
sudo mkfs.fat /dev/sdb1
sudo mkfs.ext4 /dev/sdb2
Uboot的启动地址有严格的要求,由厂家写死在片上BROM中,不可修改,所以uboot的存放位置只能根据芯片厂家的要求来存放,这个启动地址可能是1k-1M地址中的任何位置,厂家说了算,但是根据默认的规则,一般不会超过1M区域
这个启动地址可以查询厂家的手册,也可以查阅uboot官网的说明:
Allwinner SoC based boards — Das U-Boot unknown version documentation
进入uboot目录,将uboot复制到sd卡上
将sd卡拔出,插到板子上,接好串口CH340,打开串口工具,我使用的是mobaxterm。上电后能看到输出,在autoboot出现时按下空格,就可以进入uboot操作
观察uboot编译时间,如果大致符合编译进行的时间,那么就说明uboot是正常的
(2)配置uboot
1)假设有网口
我推荐使用tftp将内核搬入内存进行启动,这样就不需要反复插拔sd卡,能够减少sd卡内容损坏的可能性
1.设置tftp
这样设置(最后两个点之间的数字是网段,只要保证192.168.xxx.yyy中的xxx相同且yyy的数字不同,就可以相互ping,这个数字不得大于255):
setenv ipaddr 192.168.10.100
setenv serverip 192.168.10.110
saveenv
注意:假设设置时看见下面这样的内容,重新上电一下就好了
然后需要在主机端安装tftp服务
sudo apt install tftpd-hpa
打开配置文件,主要是第二行的tftp目录变量,随便选一个不常用的文件夹暂存要传输的文件即可
vim /etc/default/tftpd-hpa
上面编译出的内核以及设备树放入这个文件夹,像这样
回到mobaxterm, 进行测试,看见如下内容说明tftp正常
tftp 0x46000000 sun50i-h618-orangepi-zero3.dtb
2.配置uboot启动
Uboot启动主要有两个变量需要配置
第一个bootargs:指定了启动的控制台以及根文件位置,这样设置
setenv bootargs “console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait panic=10”
这条指令的意思是,指定控制台为串口,一般ttyS0这样的就是默认的串口调试设备;指定根文件为mmcblk块设备0的第二个分区,即sd卡0的第二个分区,也就是前文我们最后一个大分区,只有一张sd卡默认编号为0,多张则以此类推(这里写/dev/sda2也是可以的,都一样);rootwait panic=10,系统没进入的话等待10秒钟自动重启
注意:uboot内多条指令作为变量进行保存需要以字符串的形式存入,非同一条指令则需要“;”分隔开。
第二个bootcmd:设定了系统准备启动之前做的准备工作,一般是搬运内核、设备树到指定位置然后指定启动地址。先输入这个,看一眼内存的使用情况
bdinfo
可以看到0x40000000 – 0x4003ffff以及0xbaf02000 – 0xbfffffff是不可使用的,那么内核搬运的地址就只能是0x4003fff – 0xbaf02000之间
为了方便观察并且尽量从低地址使用,我们可以设定内核搬到0x43000000,设备树到0x46000000,两个地址不应该离得太近,避免因为相互挤占而损坏文件(M的数量级是16^5,占5位)
确定好搬运地址以后这样设置:
setenv bootcmd “tftp 0x43000000 Image.gz; tftp 0x46000000 sun50i-h618-orangepi-zero3.dtb; booti 0x43000000 – 0x46000000”
设置完以后长这样,字符串的符号“”不显示,但是你设置的时候要输入,不然就会变成多个并列的指令,而不会整合到bootcmd里一起执行,类似于
--------------------------------------------------------------------
>Setenv bootcmd tftp 0x43000000 Image.gz
>tftp 0x46000000 sun50i-h618-orangepi-zeor3.dtb
>booti 0x43000000 – 0x46000000
--------------------------------------------------------------------
而我们需要的
---------------------------------------------------------------------------------------------------------------------------------
> setenv bootcmd “tftp 0x43000000 Image.gz; tftp 0x46000000 sun50i-h618-orangepi-zero3.dtb; booti 0x43000000 – 0x46000000”
---------------------------------------------------------------------------------------------------------------------------------
2)假设你没网口
如果没有网口,那就只能把sd卡插回电脑拷贝内核和设备树
确认好磁盘号以后对第一个分区进行挂载操作,里面理应只有uboot.env 这个文件,我们需要将内核跟设备树复制进去,像这样
跟上面一样,设置bootargs和bootcmd,只是具体指令略有不同
setenv bootargs “console=ttyS0,115200 root=/dev/mmcblk0p2”
setenv bootcmd “fatload mmc 0 0x43000000 Image.gz; fatload mmc 0 0x46000000 sun50i-h618-orangepi-zero3.dtb; booti 0x43000000 – 0x46000000”
3)观察启动
如果不打断boot,滚动输出信息后看到这样的信息然后自动重启就算内核跟设备树都正常了,起不来只是因为找不到文件系统
(3)加入文件系统
我们可以自己使用busybox构建属于自己的根文件系统,也可以直接去下载现成的,我这里直接下载现成的文件系统:
Index of /ubuntu-base/releases/22.04.5
下载下来长这样
挂载分区2,可以看到里面有一个lost + found 文件夹,说明就是这个分区将下载的文件系统复制进去,然后解压
tar -xJvf rootfs.tar.gz
注意:要以root解压,不然会产生一系列的读写权限问题
解压完长这样
sudo umount /mnt
然后拔下来,插回开发板再次上电,看到上面信息后就说明完整的系统已经起来了,但是光这样我们的系统因为工具缺失以及内核臃肿,还是不能正常使用,下一节开始软件层面的配置