为树莓派3B+型号增加u-boot的支持

材料

给tf卡刷写官方镜像

直接刷写

在刷写之前,首先要判断tf卡是哪个设备,如/dev/sdb。接下来通过dd命令直接刷写img到tf卡, 命令如下

dd if=2022-01-28-raspios-bullseye-armhf-lite.img of=/dev/sdb bs=1M status=progress && sync

手动分区

通过fdisk命令,把tf卡分两个分区,第一个分区大小为256M, 第二个分区为剩余空间 第一个分区格式化为fat32(注意,fat32分区最好在windows下格式化,因为linux 使用mkfs.vfat 格式化为fat16) 第二个分区格式化为ext4

该部分我在“自己制作树莓派启动盘”有写过,这里不再赘述。

使能UART串口打印

使能bootcode串口打印

sed -i 's/BOOT_UART=0/BOOT_UART=1/' bootcode.bin

使能UART

在config.txt中配置enable_uart=1

rpi 3b+的串口知识

rpi 3b+有两个UART,一个是ttyAMA0, 一个是ttyS0,另外还有两个链接文件指向这两个设备,serial0和serial1,默认情况下/dev/serial0 –> /dev/ttyS0, /dev/serial1 –> /dev/ttyAMA0,serial0用于串口通信,serial1用于蓝牙通信。ttyAMA0比ttyS0稳定。可以通过设备树修改这两个串口的关系,让串口用ttyAMA0,让蓝牙用ttyS0。也可以通过在config.txt中配置dtoverlay=pi3-disable-bt,让/dev/serial0 –> /dev/ttyAMA0, /dev/serial1 –> /dev/ttyS0。

刷写镜像并使能UART后,启动系统,第一次启动时,init为一个脚本,该脚本会初始化一些设置,完成之后,会自动重启。

重启过程中,串口打印里有两个信息要重点关注

MESS:00:00:02.626431:0: Loading 'bcm2710-rpi-3-b-plus.dtb' to 0x100 size 0x7a22

这个设备树信息用于传给kernel指定的对象,默认是kernel7l.img, 而如果kernel=u-boot.bin, 那这个文件就是传给u-boot。

[    0.000000] Kernel command line: coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1 video=Composite-1:720x480@60i vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000  console=ttyS0,115200 console=tty1 root=PARTUUID=73ccfd8e-02 rootfstype=ext4 fsck.repair=yes rootwait

这个是内核启动的默认配置,在制作boot.scr.uimg时有用到。

编译并使用u-boot启动

编译并配置32位u-boot

编译

apt install gcc-arm-linux-gnueabi
apt install device-tree-compiler

编译器版本信息如下

gcc-arm-linux-gnueabi is already the newest version (4:11.2.0-2).
device-tree-compiler is already the newest version (1.6.1-1).

编译命令

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabi-
make distclean
make rpi_3_32b_defconfig
make -j8

配置
编译完成后,生成u-boot.bin文件,这个文件是裸机二进制文件。

新建一个boot.scr文件,内容如下
fatload mmc 0:1 ${kernel_addr_r} kernel7l.img
fatload mmc 0:1 ${fdt_addr} bcm2710-rpi-3-b-plus-kernel.dtb
setenv bootargs coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1 video=Composite-1:720x480@60i vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000  console=ttyS0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 fsck.repair=yes rootwait
bootz ${kernel_addr_r} - ${fdt_addr}

把boot.scr转化为boot.scr.uimg
./tools/mkimage -A arm -O linux -T script -C none -n boot.scr -d boot.scr boot.scr.uimg

因为kernel7l.img可以通过u-boot的bootz命令启动,所以默认情况下,不需要自己编译内核。下面只介绍编译32位内核的方法。

编译32位kernel

编译命令

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabi-
make -C linux-rpi-5.15.y O=`pwd`/rpi-515_32 distclean
make -C linux-rpi-5.15.y O=`pwd`/rpi-515_32 bcm2835_defconfig
make -C linux-rpi-5.15.y O=`pwd`/rpi-515_32 zImage dtbs -j8

编译并配置64位u-boot

编译

apt-get install gcc-aarch64-linux-gnu

编译器版本:

gcc-aarch64-linux-gnu is already the newest version (4:11.2.0-2).

编译64位u-boot
编译命令

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make distclean
make rpi_arm64_defconfig
make -j8

编译64位内核
编译命令

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make -C linux-rpi-5.15.y O=`pwd`/rpi-515_64 distclean
make -C linux-rpi-5.15.y O=`pwd`/rpi-515_64 bcmrpi3_defconfig
make -C linux-rpi-5.15.y O=`pwd`/rpi-515_64 Image dtbs -j8

配置
64位的u-boot默认没有bootz启动命令,有booti命令,该命令支持压缩的内核或者裸机内核(也就是Image文件),经过验证发现,32位的raspbian系统,可以通过32位的u-boot启动kernel7l.img文件,但是64位的u-boot无法通过booti启动kernel8文件,基于这个原因,需要自己编译内核Image裸机内核镜像,用于u-boot引导启动。

新建一个boot.scr文件,内容如下
fatload mmc 0:1 ${kernel_addr_r} Image
fatload mmc 0:1 ${fdt_addr} bcm2710-rpi-3-b-plus-kernel.dtb
setenv bootargs initcall_debug=1 coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1 video=Composite-1:720x480@60i vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000  console=ttyS0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 fsck.repair=yes rootwait
booti ${kernel_addr_r} - ${fdt_addr}

把boot.scr转化为boot.scr.uimg
./tools/mkimage -A arm -O linux -T script -C none -n boot.scr -d boot.scr boot.scr.uimg

tips:编译完内核后,当我把rpi-515_64/arch/arm64/boot/dts/broadcom/bcm2837-rpi-3-b-plus.dtb拷贝到boot分区,并重命名为bcm2710-rpi-3-b-plus-kernel.dtb时,内核启动过程中,会卡在[ 5.687754] random: crng init done, 而当我把bcm2710-rpi-3-b-plus.dtb拷贝到boot分区,并重命名为bcm2710-rpi-3-b-plus-kernel.dtb时,内核启动过程正常。

那些移植u-boot碰到的坑

我在给rpi 3b+移植u-boot时,碰到最多的问题就是UART串口问题,要么u-boot读秒时无法输入,要么内核没有打印信息,卡在‘Starting kernel …’,要么内核打印信息但是无法输入、无法登录。要么就是内核卡在某个地方不执行。所有的这些问题都和一个东西有关,那就是设备树。

u-boot本身也会依赖设备树,根据编译的配置决定的。 在uboot的configs下面,有很多预置的编译配置,我发现在u-boot-2020.04.tar.bz2版本中编译rpi_arm64_config配置时,uboot可以在读秒时,输入字符进入uboot,而编译rpi_3_32b_defconfig时,不能输入进入uboot,这里有一个有趣的点,编译rpi_arm64_config配置时,不会编译dtb设备树文件,可以理解为这个配置下的u-boot,不依赖设备树,也正因为这样,才误打误撞能在u-boot阶段输入字符。

所以如果想要编译这种不依赖设备树的32位的u-boot,可以在configs目录下,新建一个rpi_arm32_defconfig文件,文件配置如下

CONFIG_ARM=y
CONFIG_ARCH_BCM283X=y
CONFIG_SYS_TEXT_BASE=0x00008000
CONFIG_TARGET_RPI_3_32B=y
CONFIG_SYS_MALLOC_F_LEN=0x2000
CONFIG_NR_DRAM_BANKS=2
CONFIG_DISTRO_DEFAULTS=y
CONFIG_OF_BOARD_SETUP=y
CONFIG_USE_PREBOOT=y
CONFIG_PREBOOT="usb start"
CONFIG_MISC_INIT_R=y
# CONFIG_DISPLAY_CPUINFO is not set
# CONFIG_DISPLAY_BOARDINFO is not set
CONFIG_SYS_PROMPT="U-Boot> "
CONFIG_CMD_GPIO=y
CONFIG_CMD_MMC=y
CONFIG_CMD_USB=y
CONFIG_CMD_FS_UUID=y
CONFIG_OF_BOARD=y
CONFIG_ENV_FAT_INTERFACE="mmc"
CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
CONFIG_DM_KEYBOARD=y
CONFIG_DM_MMC=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_BCM2835=y
CONFIG_DM_ETH=y
CONFIG_BCMGENET=y
CONFIG_PINCTRL=y
# CONFIG_PINCTRL_GENERIC is not set
# CONFIG_REQUIRE_SERIAL_CONSOLE is not set
CONFIG_USB=y
CONFIG_DM_USB=y
CONFIG_USB_DWC2=y
CONFIG_USB_KEYBOARD=y
CONFIG_USB_HOST_ETHER=y
CONFIG_USB_ETHER_LAN78XX=y
CONFIG_USB_ETHER_SMSC95XX=y
CONFIG_DM_VIDEO=y
# CONFIG_VIDEO_BPP8 is not set
# CONFIG_VIDEO_BPP16 is not set
CONFIG_SYS_WHITE_ON_BLACK=y
CONFIG_CONSOLE_SCROLL_LINES=10
CONFIG_PHYS_TO_BUS=y
CONFIG_OF_LIBFDT_OVERLAY=y

接下来就是编译32位u-boot的常规操作

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabi-
make distclean
make rpi_arm32_defconfig
make -j8

编译完成后,u-boot.bin就不依赖设备树,在u-boot目录,也找不到dtb文件。

rpi_3_32b_defconfig 有一个配置是CONFIG_DEFAULT_DEVICE_TREE=”bcm2837-rpi-3-b”。有这个配置,uboot就会编译dtb文件,uboot也会依赖这个dtb。正常启动过程,是bootcode.bin加载内核到0x00008000,加载bcm2710-rpi-3-b-plus.dtb, 我用的2022-01-28-raspios-bullseye-armhf-lite.img, 看启动记录

MESS:00:00:02.292243:0: dtb_file 'bcm2710-rpi-3-b-plus.dtb'
MESS:00:00:02.302511:0: brfs: File read: /mfs/sd/bcm2710-rpi-3-b-plus.dtb
MESS:00:00:02.307607:0: Loading 'bcm2710-rpi-3-b-plus.dtb' to 0x100 size 0x3898

然后把跳转到0x00008000位置(内核位置)执行,把控制权交给内核。

而加入uboot后,如果uboot依赖设备树,需要注意一点,需要把编译uboot后的设备树也就是bcm2837-rpi-3-b-plus.dtb拷贝到boot目录,并且要重命名为bcm2710-rpi-3-b-plus.dtb,让bootcode像启动kernel那样,启动uboot,这时设备树和uboot是配套的,uboot串口操作才能正常。

root@kali:~/u-boot-2022.04# find . -name *.dts | grep rpi
./arch/arm/dts/bcm2837-rpi-3-b-plus.dts
./arch/arm/dts/bcm2836-rpi-2-b.dts
./arch/arm/dts/bcm2837-rpi-cm3-io3.dts
./arch/arm/dts/bcm2711-rpi-4-b.dts
./arch/arm/dts/bcm2837-rpi-3-a-plus.dts
./arch/arm/dts/bcm2835-rpi-zero-w.dts
./arch/arm/dts/bcm2837-rpi-3-b.dts
./arch/arm/dts/bcm2835-rpi-b-plus.dts
./arch/arm/dts/bcm2835-rpi-b-rev2.dts
./arch/arm/dts/bcm2835-rpi-a.dts
./arch/arm/dts/bcm2835-rpi-a-plus.dts
./arch/arm/dts/bcm2835-rpi-zero.dts
./arch/arm/dts/bcm2835-rpi-b.dts
./arch/arm/dts/bcm2835-rpi-cm1-io1.dts

树莓派有两个UART在u-boot源码的arch/arm/dts/bcm2837-rpi-3-b-plus.dts文件中,配置了

	chosen {
		/* 8250 auxiliary UART instead of pl011 */
		stdout-path = "serial1:115200n8";
	};

......

/* uart0 communicates with the BT module */
&uart0 {
	pinctrl-names = "default";
	pinctrl-0 = <&uart0_ctsrts_gpio30 &uart0_gpio32 &gpclk2_gpio43>;
	status = "okay";

	bluetooth {
		compatible = "brcm,bcm43438-bt";
		max-speed = <2000000>;
		shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>;
	};
};

/* uart1 is mapped to the pin header */
&uart1 {
	pinctrl-names = "default";
	pinctrl-0 = <&uart1_gpio14>;
	status = "okay";
};

而树莓派支持在config.txt中配置dtoverlay=pi3-disable-bt, 如果配置了这个,那么串口关系如下

/dev/serial0 --> /dev/ttyAMA0
/dev/serial1 --> /dev/ttyS0

到目前为止我也没搞清楚这些UART之间的关联,我猜测的应该是u-boot及内核默认或者设置了哪个串口和gpio的pin6,8,10相关联,比如u-boot设置了ttyS0和gpio的pin6,8,10相关联,但是通过dtoverlay=pi3-disable-bt,bootcode.bin设置了ttyAMA0和gpio的pin6,8,10相关联。这样导致了各种问题。以上存粹是我的猜想,有个问题是如果关联不恰当,那应该是输入输出都没有了,我经常碰到的是有输出但是没有输入。

sd卡的boot分区一定要有bcm2710-rpi-3-b-plus.dtb或者bcm2710-rpi-3-b.dtb文件,因为bootcode.bin需要读取这个文件并加载到内存,如果没有这两个文件之一,则卡在bootcode不进行后续操作。启动过程中,有两次加载设备树的过程,第一次是bootcode.bin加载,会先尝试加载bcm2710-rpi-3-b-plus.dtb,如果找到,就加载到0x100处如果没找到,就尝试加载bcm2710-rpi-3-b.dtb,如果找到,就加载到0x100处,如果没找到,就停止运行。

设备树加载好之后,会去config.txt中找kernel的配置,如果没有配置,默认32位加载kernel7l.img, 64位加载kernel8.img.

而如果有配置kernel,例如kernel=u-boot.bin, 则bootcode会把u-boot.bin加载到内存中,32位加载到0x00008000, 64位加载到0x00080000, 接下来跳转到对应内存地址执行,控制权交给u-boot。注意这里。启动的程序要和对应的设备树配套即可。比如默认加载kernel7l.img, 那就用镜像默认自带的bcm2710-rpi-3-b-plus.dtb。同样的,如果启动的是u-boot,那应该把u-boot源码目录下的./arch/arm/dts/bcm2837-rpi-3-b-plus.dtb拷贝到sd卡的boot分区,并重命名为bcm2710-rpi-3-b-plus.dtb即可。

u-boot编译过程中,会把dts编译为dtb,比如bcm2837-rpi-3-b-plus.dts,编译之后就变成bcm2837-rpi-3-b-plus.dtb,这个bcm2837-rpi-3-b-plus.dtb就是u-boot所依赖的设备树,需要重命名为bcm2710-rpi-3-b-plus.dtb放到sd卡的boot分区。之所以要重命名是因为bootcode.bin和start.elf这些固件都是不开源的,这些固件默认只读取bcm2710-rpi-3-b-plus.dtb或者bcm2710-rpi-3-b.dtb,所以必须要重命名。 u-boot和依赖的设备树匹配后,在启动过程u-boot提示Hit any key to stop autoboot: 0时,键盘输入才会有响应,否则会造成键盘输入无响应,无法进入u-boot界面。

如果kernel=u-boot,那么cmdline.txt文件就没有用,内容不重要,如果kernel=kernel7l.img等linux内核,则cmdline.txt就是传输给内核的参数,很重要,必须要填写正确才行。如果想要内核有打印,就要把quiet这个参数删除。

我根据正常情况下,也就是不通过u-boot直接启动内核时的串口打印,看到如下信息

[    0.000000] Kernel command line: 

可以把 cmdline.txt设置为上面打印的参数一致。

coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1 video=Composite-1:720x480@60i vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000 console=ttyAMA0,115200 console=tty1 root=PARTUUID=2e499f85-02 rootfstype=ext4 fsck.repair=yes rootwait

console=ttyS0,115200,系统启动时默认是console=ttyAMA0,115200, 经测试发现console=ttyS0,115200能正常输入输出,而console=ttyAMA0,115200会出现无法输入的情况。

console=tty1是指hdmi输出有个终端界面,也就是告诉内核有两个终端,一个是串口,通过ttyS0以115200的波特率通信,另一个是tty1终端。默认情况下,内核可能会有quiet的选项,要把这个选项去掉内核才会打印,不然就是静默的执行,不输出信息。

这里又有另外一个坑,console=ttyAMA0建议修改为console=ttyS0,反正这样能正常工作,内核启动后能输入数据,而如果console=ttyAMA0,内核启动过程有打印,但是提示Raspbian GNU/Linux 11 raspberrypi ttyAMA0

raspberrypi login: 会造成无法输入。原因不明。另外如果console=ttyS0,则config.txt中,就不能使能dtoverlay=pi3-disable-bt,这句不能出现,要么不写,要么注释掉。

建议串口这么配置:

  1. 根据我上面的操作,dtoverlay=pi3-disable-bt不能在config.txt中配置,否则串口会异常,如果要配置,那么u-boot,内核启动参数也要同步修改串口才能正常。
  2. cmdline和boot.scr.uimg 都需要配置console=ttyS0

uboot要启动内核,需要指定boot.scr.uimg文件内容,首先配置boot.scr

fatload mmc 0:1 ${kernel_addr_r} kernel7l.img
fatload mmc 0:1 ${fdt_addr} bcm2710-rpi-3-b-plus-kernel.dtb
setenv bootargs coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1 video=Composite-1:720x480@60i vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000  console=ttyS0,115200 console=tty1 root=PARTUUID=2e499f85-02 rootfstype=ext4 fsck.repair=yes rootwait
bootz ${kernel_addr_r} - ${fdt_addr}

${kernel_addr_r}是u-boot的变量,默认设置为0x00080000, ${fdt_addr}默认为 0x2effc100

fatload mmc 0:1 ${kernel_addr_r} kernel7l.img
fatload mmc 0:1 ${fdt_addr} bcm2710-rpi-3-b-plus-kernel.dtb

这两个命令分别是把fat分区的kernel7l.img文件加载到${kernel_addr_r}这个内存地址中

把fat分区的bcm2710-rpi-3-b-plus-kernel.dtb文件加载到${fdt_addr}这个内存地址中。这里bcm2710-rpi-3-b-plus-kernel.dtb是内核依赖的设备树文件,其实就是把原先镜像的bcm2710-rpi-3-b-plus.dtb重命名了,避免和u-boot依赖的设备树冲突。

基于集成了u-boot的tf卡,制作DIY rpi 3b+镜像文件

把tf卡挂载到pc上,在pc上执行下面的脚本,即可生成rpi镜像。 操作之前,需要先修改img,tf_boot,tf_root这几个变量,如果镜像较大,也要调整count的大小。 实现机制很简单,就是基于loop设备生成文件镜像,像操作磁盘一样操作文件,对文件进行分区,格式化,挂载,文件拷贝等操作。

#!/bin/sh

img=rpi.img
tf_boot=/media/root/boot/
tf_root=/media/root/rootfs/

dd if=/dev/zero of=$img bs=1MB count=2500
parted $img --script -- mklabel msdos
parted $img --script -- mkpart primary fat32 8192s 532480s
parted $img --script -- mkpart primary ext4 534528s -1

loopdevice=`losetup -f --show $img`
kpartx -va $loopdevice
device=`echo $loopdevice| awk -F'/' '{print $3}'`
device="/dev/mapper/${device}"
partBoot="${device}p1"
partRoot="${device}p2"
mkfs.vfat $partBoot
mkfs.ext4 $partRoot

tmpdir=/tmp/media
mkdir $tmpdir
mount -t vfat $partBoot $tmpdir
cp -rfp $tf_boot/* $tmpdir
umount $tmpdir
mount -t ext4 $partRoot $tmpdir
cp -rfp $tf_root/* $tmpdir
# cd $tmpdir
# dump -0uaf - $tf_root | restore -rf -
# cd
umount $tmpdir
kpartx -d $loopdevice
losetup -d $loopdevice
rmdir $tmpdir

参考材料

[1] https://a-delacruz.github.io/ubuntu/rpi3-setup-64bit-uboot.html
[2] https://a-delacruz.github.io/ubuntu/rpi3-setup-64bit-kernel
[3] https://blog.51cto.com/u_15304255/3166221

Table of Contents