目录

为荔枝派Zero制作Linux系统镜像

最近购入了一块LicheePi Zero开发板,使用全志V3s作为主控芯片。本文介绍如何从零开始为荔枝派制作一个可以启动的Linux系统镜像。

配置编译环境

本文所有编译工作均基于Ubuntu 20.04LTS系统。

安装交叉编译器

V3s为ARM架构,为了能够生成ARM架构的代码,首先需要一个交叉编译器,其中最常用的为linaro公司推出的arm-linux-gnueabihf交叉编译器。可以前往Linaro Toolchain页面下载x86_64版本的交叉编译器。截至本文完成之时,编译器最新版本为7.5.0,下文也将以这个版本为例进行说明。

首先,下载交叉编译器,并安装到/opt目录下。

cd ~/Download
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
sudo tar xf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt
sudo mv gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf arm-linux-gnueabihf-gcc #重命名文件夹为arm-linux-gnueabihf-gcc

下载并安装后,还需要将编译器所在路径加入到PATH中,以便make编译时调用。

echo "PATH=\$PATH:/opt/arm-linux-gnueabihf-gcc/bin" >> ~/.bashrc
source ~/.bashrc

编译系统镜像

编译U-boot

首先,从GitHub拉下荔枝派官方提供的修改过的U-boot源码。

cd ~/code/licheepi-zero
git clone https://github.com/Lichee-Pi/u-boot -b v3s-current

在编译之前,还需要安装一些依赖。

sudo apt install u-boot-tools python3-distutils python3-dev swig build-essential libncurses5-dev git 

接下来,进入代码文件夹,根据使用的屏幕分辨率,直接编译即可。

cd u-boot
# make ARCH=arm LicheePi_Zero_480x272LCD_defconfig
# make ARCH=arm LicheePi_Zero_800x480LCD_defconfig #根据你使用的屏幕分辨率进行选择
make ARCH=arm LicheePi_Zero_defconfig #没有屏幕
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

编译生成的U-bootu-boot的根目录下,名为u-boot-sunxi-with-spl.bin

编译Linux内核

从GitHub拉下荔枝派官方提供的Linux源码。这里我使用了5.2版本的Linux内核。

cd ~/code/licheepi-zero
git clone https://github.com/Lichee-Pi/linux.git -b zero-5.2.y

此仓库中的源码已经适配了音频、有线网卡等的驱动,并已修改完毕dtb。拉取完成后,在编译前还需要安装一些依赖。

sudo apt install flex bison libssl-dev

之后,进入源码文件夹,使用官方提供的sunxi_defconfig配置文件,对代码进行配置。

cd linux
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- sunxi_defconfig

默认的配置文件中,并没有开启有线网卡的驱动。如果需要使用有线网卡,还需要在menuconfig中开启。首先执行

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make menuconfig

在菜单中,选择下面菜单项中的网卡驱动支持即可。

Device Drivers —>
 Network device support —>
  Ethernet driver support —>
   [*]   STMicroelectronics devices
     <*>     STMicroelectronics 10/100/1000/EQOS Ethernet driver
     <*>       STMMAC Platform bus support
     < >         Support for snps,dwc-qos-ethernet.txt DT binding.
     <*>         Generic driver for DWMAC
     <*>         Allwinner GMAC support
     <*>         Allwinner sun8i GMAC support

此仓库中源码内的dts文件中只启用了UART0这一个串口作为调试串口使用。若需要使用所有3个串口,还需要对dts文件进行修改。首先对\arch\arm\boot\dts\sun8i-v3s.dtsi进行修改,加入对串口引脚的定义。

uart0_pins_a: uart0@0 { pins = "PB8", "PB9"; function = "uart0";bias-pull-up; };
uart1_pins_a: uart1@0 { pins = "PE21", "PE22"; function = "uart1";bias-pull-up; };
uart2_pins_a: uart2@0 { pins = "PB0", "PB1"; function = "uart2";bias-pull-up; };

然后修改\arch\arm\boot\dts\sun8i-v3s-licheepi-zero.dts,启动这3个串口。

&uart0 { pinctrl-0 = <&uart0_pins_a>; pinctrl-names = "default";status = "okay"; };
&uart1 { pinctrl-0 = <&uart1_pins_a>; pinctrl-names = "default";status = "okay"; };
&uart2 { pinctrl-0 = <&uart2_pins_a>; pinctrl-names = "default";status = "okay"; };

配置完成后,对源码进行编译。

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

得到内核镜像zImage和设备树sun8i-v3s-licheepi-zero.dtb,分别位于arch/arm/boot/zImagearch/arm/boot/dts/sun8i-v3s-licheepi-zero.dtb

编译rootfs

BuildRoot网站上下载Buildroot源码并解压。这里我以Buildroot 2020.02为例。

cd ~/Downloads
wget https://buildroot.org/downloads/buildroot-2020.02.8.tar.gz
tar xf buildroot-2020.02.8.tar.gz -C ~/code/licheepi-zero
cd ~/code/licheepi-zero/buildroot-2020.02.8
mv buildroot-2020.02.8 buildroot-2020.02 # 重命名
make menuconfig

在编译之前,需要安装BuildRoot的依赖项。

sudo apt install python texinfo unzip

首先,对CPU架构Target options进行配置。

Target Architecture (ARM (little endian))  --->
Target Binary Format (ELF)  --->
Target Architecture Variant (cortex-A7)  --->
Target ABI (EABIhf)  --->
Floating point strategy (VFPv4-D16)
ARM instruction set (ARM)

然后,对工具链Toolchain进行配置。

    Toolchain type (External toolchain)  --->
    *** Toolchain External Options ***
    Toolchain (Custom toolchain)  --->
    Toolchain origin (Pre-installed toolchain)
(/opt/arm-linux-gnueabihf-gcc) Toolchain path
(arm-linux-gnueabihf) Toolchain prefix
    External toolchain gcc version (7.x)  --->
    External toolchain kernel headers series (4.10.x)  --->
    External toolchain C library (glibc/eglibc)  --->
[*] Toolchain has SSP support?
[*]   Toolchain has SSP strong support?
[*] Toolchain has RPC support?
[*] Toolchain has C++ support?
[ ] Toolchain has D support?
[ ] Toolchain has Fortran support?
[ ] Toolchain has OpenMP support?
[ ] Copy gdb server to the Target
    *** Host GDB Options ***
[ ] Build cross gdb for the host
    *** Toolchain Generic Options ***
[ ] Copy gconv libraries
()  Extra toolchain libraries to be copied to target
[*] Enable MMU support
()  Target Optimizations
()  Target linker options
[ ] Register toolchain within Eclipse Buildroot plug-in

接下来可以在Target packages中选择自己需要的模块。这里以minicompython3为例。

Target packages  --->
   Hardware handling  --->
     [*] minicom
   Interpreter languages and scripting  --->
     [*] python3
           python3 module format to install (.py sources and .pyc compiled)  --->
           core python3 modules  --->
         External python modules  --->
           [*] python-pip
           [*] python-serial

配置完成后保存退出,编译。

make

输出文件在output/images/rootfs.tar

烧录镜像

直接烧录到TF卡

假设TF卡的设备是/dev/sdb。接下来使用GParted对TF卡进行格式化与分区。

危险
请一定注意设备号是否正确,否则可能会损坏电脑硬盘内的数据。

首先删除TF卡上的所有分区。

https://img.yuanze.wang/posts/build-boot-image-for-v3s/gparted1.png
删除所有分区

然后,新建存放系统镜像设备树与启动脚本的boot分区。右键点击未分配的空间,选择新建,新建一个前部有1M空闲空间,大小为32MFAT32分区,命名为boot

https://img.yuanze.wang/posts/build-boot-image-for-v3s/gparted2.png
新建boot分区

接下来,选择剩余的所有空间,创建一个ext4格式的rootfs分区(即为点击新建后默认的选项)。

https://img.yuanze.wang/posts/build-boot-image-for-v3s/gparted3.png
新建roosfs分区

新建完分区的整块磁盘看起来像这样。

https://img.yuanze.wang/posts/build-boot-image-for-v3s/gparted4.png
TF卡整体分区

请再三确认,之前格式化的时候创建boot分区时,分区前预留了1M的空间。确认无误后,点击绿色的对勾提交更改,TF卡格式化就完毕了。提交更改之后,为了防止系统无法立即挂载新的分区,建议将读卡器拔出后重新插入。

接下来便是写入各个部分的代码了。首先使用下列命令写入u-boot。写入之前请再次确认读卡器的设备号为sdb,以防损坏硬盘内的数据。

cd ~/code/licheepi-zero/u-boot
sudo dd if=u-boot-sunxi-with-spl.bin of=/dev/sdb bs=1024 seek=8

接下来,将licheepi-zero/linux/arch/arm/boot/zImagelicheepi-zero/linux/arch/arm/boot/dts/sun8i-v3s-licheepi-zero.dtb两个文件复制到boot分区中。

然后,将licheepi-zero/buildroot-2020.02/output/images/rootfs.tar解压到rootfs分区中。这必须要使用命令行操作,请确认rootfs挂载的路径,通常来说为/media/用户名/rootfs

卷标设置
若未对分区设置的正确卷标,此时系统将自动挂载卷标为卷的uuid,它看起来像4102a4fc-7b8a-4f3b-9c68-31c0ef9ffac4。下面指令中具体的卷标或uuid需要根据实际情况进行设置。
sudo tar xf ~/code/licheepi-zero/buildroot-2020.02/output/images/rootfs.tar -C /media/wangyz/rootfs
sudo sync # 将更改写入卡中

最后,编写启动脚本。启动脚本用于指定u-boot的启动行为,包括内核与设备树的文件名、应该将哪个分区挂载为rootfs等。在~/code/licheepi-zero/目录下,新建boot.cmd,将下面内容写入。

setenv bootargs console=ttyS0,115200 panic=5 rootwait root=/dev/mmcblk0p2 earlyprintk rw
load mmc 0:1 0x41000000 zImage
load mmc 0:1 0x41800000 sun8i-v3s-licheepi-zero.dtb
bootz 0x41000000 - 0x41800000

然后,编译启动脚本。

mkimage -C none -A arm -T script -d boot.cmd boot.scr

得到的boot.scr即为启动脚本,将其复制到boot分区,即完成了可启动TF卡的制作。

制作烧录镜像

上述操作比较麻烦,且不方便将可启动的内存卡镜像发给他人。因此,可以使用脚本将上述镜像打包为一个烧录文件,直接烧录进TF卡。此处参考了坑网帖子分享全志主线u-boot/linux 打包 TF/SD/SDNAND 镜像脚本并做出了一些修改。只需要将脚本文件内容新建为pack.sh,放入~/code/licheepi-zero路径下,使用sudo运行即可。运行前请确认脚本中的各个路径是否正确。

#!/bin/bash
_UBOOT_SIZE=1
###第一个分区(FAT)大小,单位MiB##
_P1_SIZE=32
###TF卡镜像文件名称###
_IMG_FILE='a20_generic_sdcard.bin'
###TF卡镜像文件大小, 单位MiB###
_IMG_SIZE=512
###存放各个文件的路径
temp_root_dir=$PWD
_UBOOT_FILE="${temp_root_dir}/u-boot/u-boot-sunxi-with-spl.bin"
_KERNEL_IMAGE_FILE="${temp_root_dir}/linux/arch/arm/boot/zImage"
_DTB_FILE="${temp_root_dir}/u-boot/arch/arm/dts/sun8i-v3s-licheepi-zero.dts"
_ROOTFS_TGZ_FILE="${temp_root_dir}/buildroot-2020.02/output/images/rootfs.tar"
###初始化镜像文件###
dd if=/dev/zero of=$_IMG_FILE bs=1M count=$_IMG_SIZE
###判断镜像文件是否初始化成功###
if [ $? -ne 0 ]
then
        echo  "getting error in creating dd img!"
        exit
fi
###获取一个循环设备###
_LOOP_DEV=$(sudo losetup -f)
##再次判断此设备是否存在###
if [ -z $_LOOP_DEV ]
then
        echo  "can not find a loop device!"
        exit
fi
###把镜像文件和循环设备关联###
sudo losetup $_LOOP_DEV $_IMG_FILE
if [ $? -ne 0 ]
then
        echo  "dd img --> $_LOOP_DEV error!"
        sudo losetup -d $_LOOP_DEV >/dev/null 2>&1 && exit
fi
echo  "--->creating partitions for tf image ..."
###分区###
cat <<EOT |sudo  sfdisk ${_IMG_FILE}
${_UBOOT_SIZE}M,${_P1_SIZE}M,c
,,L
EOT
###格式化###
sleep 2
sudo partx -u $_LOOP_DEV
sudo mkfs.vfat ${_LOOP_DEV}p1 ||exit
sudo mkfs.ext4 ${_LOOP_DEV}p2 ||exit
if [ $? -ne 0 ]
then
        echo  "error in creating partitions"
        sudo losetup -d $_LOOP_DEV >/dev/null 2>&1 && exit
fi
###u-boot写到TF卡8K偏移处###
echo  "--->writing u-boot-sunxi-with-spl to $_LOOP_DEV"
sudo dd if=$_UBOOT_FILE of=$_LOOP_DEV bs=1024 seek=8
if [ $? -ne 0 ]
then
        echo  "writing u-boot error!"
        sudo losetup -d $_LOOP_DEV >/dev/null 2>&1 && exit
fi
###新建 p1,p2 目录,并挂载TF卡两个分区###
sudo sync
mkdir -p ${temp_root_dir}/output/p1 >/dev/null 2>&1
mkdir -p ${temp_root_dir}/output/p2 > /dev/null 2>&1
sudo mount ${_LOOP_DEV}p1 ${temp_root_dir}/output/p1
sudo mount ${_LOOP_DEV}p2 ${temp_root_dir}/output/p2
echo  "--->copy boot and rootfs files..."
sudo rm -rf  ${temp_root_dir}/output/p1/* && sudo rm -rf ${temp_root_dir}/output/p2/*
###复制zImage, dtb, boot.scr 等文件到第一分区 ###
sudo cp ${_KERNEL_IMAGE_FILE} ${temp_root_dir}/output/p1/zImage &&\
sudo cp ${_DTB_FILE} ${temp_root_dir}/output/p1/ &&\
sudo mkimage -C none -A arm -T script -d ${temp_root_dir}/boot.cmd ${temp_root_dir}/output/p1/boot.scr
echo "--->p1 done~"
###解压 rootfs.tgz 到第二分区###
sudo tar xf ${_ROOTFS_TGZ_FILE} -C ${temp_root_dir}/output/p2/ &&\
echo "--->p2 done~"
###同步, 等待, 卸载, 退出###
sudo sync
sleep 2
sudo umount ${temp_root_dir}/output/p1 ${temp_root_dir}/output/p2  && sudo losetup -d $_LOOP_DEV
if [ $? -ne 0 ]
then
        echo  "umount or losetup -d error!!"
        exit
fi
sudo rm -rf output