程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3588 - uboot引导方式介绍

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T6开发板
eMMC256GB
LPDDR416GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot2017.09
linux6.1
----------------------------------------------------------------------------------------------------------------------------

在前面的文章我们对Rockhip Linux SDK进行了深入分析,其中涉及到了SDK编译过程、编译源码,具体可以参考:

此外,我们还是深入分析了Recovery模式下的系统升级功能,具体可参考:

接下来我们将尝试在NanoPC-T6开发板实现系统升级功能,当然我们还期望当根文件系统损坏时,开发板能够通过按住GPIO口进入到recovery系统恢复正常系统。由于该系列内容较长,我们分为多篇文章介绍:

一、uboot启动方式

既然要实现在NanoPC-T6开发板实现系统升级功能,我们就需要了解uboot启动内核的方式,并制作以下分区镜像;

  • misc.imgmisc分区是一个没有文件系统的分区,用于存放一些引导配置参数;
  • recovery.img:由kernel + dtb + ramdisk组成,主要用于升级操作;

uboot会根据misc分区存放的字段来判断将要引导的系统是normal系统还是recovery系统。

1.1 系统固件

我们使用的是NanoPC-T6开发板,这里我们就去下载官方提供的固件,关于固件的下载和烧烤可以参考《Rockchip RK3588- 移植uboot 2017.09 & linux 6.1(友善之家脚本方式)》。

这里我们选择debian-bullseye-desktop-arm64-images.tgz作为测试使用的镜像文件,将debian-bullseye-desktop-arm64-images.tgz(位于"\03_分区镜像文件"目录下,以实际下载的文件为准)拷贝到/work/sambashare/rk3588/friendly/sd-fuse_rk3588目录下;

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# ll debian*
-rwxrw-rw- 1 root root 1590466719 Dec  3 01:49 debian-bullseye-desktop-arm64-images.tgz*
-rwxrw-rw- 1 root root         75 Nov 18 19:05 debian-bullseye-desktop-arm64-images.tgz.hash.md5*
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# tar -xvzf debian-bullseye-desktop-arm64-images.tgz

解压得到debian-bullseye-desktop-arm64文件夹;

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# ll debian-bullseye-desktop-arm64
-rw-r--r--  1 root root    8072140 May 28  2023 boot.img
-rw-r--r--  1 root root       1424 May 28  2023 dtbo.img
-rw-r--r--  1 root root     307200 Sep  8 23:33 idbloader.img
-rw-r--r--  1 root root         64 Nov 17 10:03 info.conf
-rw-r--r--  1 root root   35551252 Nov 16 16:17 kernel.img
-rw-r--r--  1 root root     471488 Sep  8 23:33 MiniLoaderAll.bin
-rw-r--r--  1 root root      49152 May 28  2023 misc.img
-rw-r--r--  1 root root        470 Nov 17 10:03 parameter.txt
-rw-r--r--  1 root root    6227456 Nov 16 16:17 resource.img
-rw-r--r--  1 root root 3992675220 Nov 17 10:03 rootfs.img
-rw-r--r--  1 root root    4194304 Sep  8 23:33 uboot.img
-rw-r--r--  1 root root     159868 Nov 17 10:03 userdata.img

可以看到解压的文件已经包含了misc.img,但是并没有recovery.img

1.1.1 系统分区介绍

parameter.txt保存着分区信息:

FIRMWARE_VER: 12.0
MACHINE_MODEL: RK3588
MACHINE_ID: 007
MANUFACTURER: RK3588
MAGIC: 0x5041524B
ATAG: 0x00200800
MACHINE: NanoPi6
CHECK_MASK: 0x80
PWR_HLD: 0,0,A,0,1
TYPE: GPT
CMDLINE: mtdparts=rk29xxnand:0x00002000@0x00004000(uboot),0x00002000@0x00006000(misc),0x00002000@0x00008000(dtbo),0x00008000@0x0000a000(resource),0x00014000@0x00012000(kernel),0x00010000@0x00026000(boot),0x00010000@0x00036000(recovery),0x007c0000@0x00046000(rootfs),-@0x00806000(userdata:grow)

解析信息如下:

Number Name 镜像文件 Start (sector) End (sector) Size
1 uboot uboot.img 0x4000(16384) 0x5FFF 4M
2 misc misc.img 0x6000(24576) 0x7FFF 4M
3 dtbo dtbo.img 0x8000(32768) 0x9FFF 4M
4 resource resource.img 0xa000(40960) 0x11FFF 16MB
5 kernel kernel.img 0x12000(73728) 0x25FFF 40MB
6 boot boot.img 0x26000(155648) 0x35FFF 32MB
7 recovery recovery.img 0x36000(221184) 0x45FFF 32MB
8 rootfs rootfs.img 0x46000(286720) 0x804FFF 3.968GB
9 userdata userdata.img 0x806000(8413184) -

其中:

  • uboot分区:供uboot编译出来的uboot.img
  • misc分区:引导参数分区,供misc.img,给recovery使用;
  • dtbo::供kernel编译出来的dtbo.img
  • resource:资源分区,由设备树、图片资源文件组成,不包含内核;
  • boot:供kernel编译出来的boot.img(可能是FIT uImage镜像格式,也有可能是 Android bootimg镜像格式);
  • kernel:供kernel编译出来的kernel.img(由tools/mkkrnlimg工具编译内核镜像Image文件得到);
  • recovery分区:供recovery编译出的recovery.imgkernel + dtb + ramdisk);
  • rootfs分区:供buildrootdebianyocto编出来的rootfs.img
  • userdata分区:供APP临时生成文件或给最终用户使用,挂载在/userdata目录下。

从上面我们可以看到这里有两个分区时存放了内核镜像,分别是bootkernel,那问题来了,uboot启动到底使用的是哪个内核呢?

1.1.2 生成统一固件

debian-bullseye-desktop-arm64目录下的镜像文件重新打包成SD卡固件:

root@@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# sudo ./mk-sd-image.sh debian-bullseye-desktop-arm64/
Creating RAW image: out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img (7800 MB)
---------------------------------
记录了0+0 的读入
记录了0+0 的写出
0字节已复制,0.0001181 s,0.0 kB/s
----------------------------------------------------------------
[out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img] capacity = 7438MB, 7799999488 bytes
current out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img partition:
----------------------------------------------------------------
parsing ./debian-bullseye-desktop-arm64//parameter.txt:
create new GPT 9:
----------------------------------------------------------------
copy from: ./debian-bullseye-desktop-arm64 to out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img
 [RAW. 0]:      300 KB | ./debian-bullseye-desktop-arm64/idbloader.img  > 100% : done.
 [RAW. 1]:     4096 KB | ./debian-bullseye-desktop-arm64/uboot.img      > 100% : done.
 [RAW. 2]:       48 KB | ./debian-bullseye-desktop-arm64/misc.img       > 100% : done.
 [RAW. 3]:        1 KB | ./debian-bullseye-desktop-arm64/dtbo.img       > 100% : done.
 [RAW. 4]:     2518 KB | ./debian-bullseye-desktop-arm64/resource.img   > 100% : done.
 [RAW. 5]:    34590 KB | ./debian-bullseye-desktop-arm64/kernel.img     > 100% : done.
 [RAW. 6]:     7882 KB | ./debian-bullseye-desktop-arm64/boot.img       > 100% : done.
 [RAW. 8]:  3907280 KB | ./debian-bullseye-desktop-arm64/rootfs.img     > 100% : done.
 [RAW. 9]:      156 KB | ./debian-bullseye-desktop-arm64/userdata.img   > 100% : done.
----------------------------------------------------------------
---------------------------------
RAW image successfully created (21:09:10).
-rw-r--r-- 1 root root 7799999488  7月 14 21:09 

sh脚本内部调用了Rockchip官方提供的打包工具sd_update生成的统一固件,由于打包工具并不开源,所以无法研究源码。

不过我们大致可以猜测出应该就是做了一个镜像文件,然后按照parameter.txt进行划分分区,并将各个分区镜像依次烧录进去。

点击查看代码
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588$ ll out/
-rw-r--r--  1 root root 7799999488  7月 14 21:09 rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img

记录了7438+1 的读入
记录了7438+1 的写出
7799999488字节(7.8 GB,7.3 GiB)已复制,955.721 s,8.2 MB/s

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588$ sfdisk -J out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img /dev/
{
   "partitiontable": {
      "label": "gpt",
      "id": "73987B6B-4974-4C94-A3E8-58AB2EB7A946",
      "device": "out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img",
      "unit": "sectors",
      "firstlba": 34,
      "lastlba": 15234340,
      "sectorsize": 512,
      "partitions": [
         {
            "node": "out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img1",
            "start": 16384,
            "size": 8192,
            "type": "F808D051-1602-4DCD-9452-F9637FEFC49A",
            "uuid": "B750E44E-833F-4A30-C38C-B117241D84D4",
            "name": "uboot"
         },{
            "node": "out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img2",
            "start": 24576,
            "size": 8192,
            "type": "C6D08308-E418-4124-8890-F8411E3D8D87",
            "uuid": "A1C81622-7741-47AD-B846-C6972488D396",
            "name": "misc"
         },{
            "node": "out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img3",
            "start": 32768,
            "size": 8192,
            "type": "2A583E58-486A-4BD4-ACE4-8D5454E97F5C",
            "uuid": "43784A32-A03D-4ADE-92C6-EDE64FF9B794",
            "name": "dtbo"
         },{
            "node": "out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img4",
            "start": 40960,
            "size": 32768,
            "type": "6115F139-4F47-4BAF-8D23-B6957EAEE4B3",
            "uuid": "000B305F-484A-4582-9090-4AD0099D47BD",
            "name": "resource"
         },{
            "node": "out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img5",
            "start": 73728,
            "size": 81920,
            "type": "A83FBA16-D354-45C5-8B44-3EC50832D363",
            "uuid": "24EEB649-277F-4C11-FFEB-D9F20027A83B",
            "name": "kernel"
         },{
            "node": "out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img6",
            "start": 155648,
            "size": 65536,
            "type": "500E2214-B72D-4CC3-D7C1-8419260130F5",
            "uuid": "1CAC805F-726A-495A-FD35-821355A6E7E8",
            "name": "boot"
         },{
            "node": "out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img7",
            "start": 221184,
            "size": 65536,
            "type": "E099DA71-5450-44EA-AA9F-1B771C582805",
            "uuid": "2BFEE623-D83C-426A-AB80-21732C9BB7D3",
            "name": "recovery"
         },{
            "node": "out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img8",
            "start": 286720,
            "size": 8126464,
            "type": "AF12D156-5D5B-4EE3-B415-8D492CA12EA9",
            "uuid": "B2AF085D-A675-48C6-C437-F6D557FF4744",
            "name": "rootfs"
         },{
            "node": "out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img9",
            "start": 8413184,
            "size": 6821157,
            "type": "8EB9EE49-E963-4BB6-FD75-F30618DF9DCD",
            "uuid": "2D9E7B61-1B31-47E7-EE0D-8CEC26D42EF6",
            "name": "userdata"
         }
      ]
   }
}
1.1.3 制作SD启动卡

我们将SD卡插入PC上,在虚拟机ubuntu中运行demsg查看新接入的设备;

[36809.524292] usb 2-1: USB disconnect, device number 2
[36813.382382] usb 2-1: new high-speed USB device number 3 using ehci-pci
[36813.657882] usb 2-1: New USB device found, idVendor=14cd, idProduct=1212, bcdDevice= 1.00
[36813.657887] usb 2-1: New USB device strings: Mfr=1, Product=3, SerialNumber=2
[36813.657889] usb 2-1: Product: Mass Storage Device
[36813.657890] usb 2-1: Manufacturer: Generic
[36813.657891] usb 2-1: SerialNumber: 121220160204
[36813.660529] usb-storage 2-1:1.0: USB Mass Storage device detected
[36813.661135] scsi host33: usb-storage 2-1:1.0
[36814.676011] scsi 33:0:0:0: Direct-Access     Mass     Storage Device   1.00 PQ: 0 ANSI: 0 CCS
[36814.677119] sd 33:0:0:0: Attached scsi generic sg2 type 0
[36814.681851] sd 33:0:0:0: [sdb] 62333952 512-byte logical blocks: (31.9 GB/29.7 GiB)
[36814.685829] sd 33:0:0:0: [sdb] Write Protect is off
[36814.685833] sd 33:0:0:0: [sdb] Mode Sense: 03 00 00 00
[36814.690127] sd 33:0:0:0: [sdb] No Caching mode page found
[36814.690132] sd 33:0:0:0: [sdb] Assuming drive cache: write through
[36814.713610]  sdb: sdb1
[36814.714055] sd 33:0:0:0: [sdb] Attached SCSI removable disk

可以看到SD卡对应的设备节点为/dev/sdb,对应1个分区sdb1

root@@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# sudo ls /dev/sdb*
/dev/sdb  /dev/sdb1
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# df -hT
文件系统       类型      容量  已用  可用 已用% 挂载点
udev           devtmpfs  3.9G     0  3.9G    0% /dev
tmpfs          tmpfs     791M  3.6M  787M    1% /run
/dev/sda5      ext4       98G   69G   24G   75% /
tmpfs          tmpfs     3.9G     0  3.9G    0% /dev/shm
tmpfs          tmpfs     5.0M  4.0K  5.0M    1% /run/lock
tmpfs          tmpfs     3.9G     0  3.9G    0% /sys/fs/cgroup
/dev/sda1      vfat      511M  4.0K  511M    1% /boot/efi
/dev/loop15    squashfs  497M  497M     0  100% /snap/gnome-42-2204/132
tmpfs          tmpfs     791M     0  791M    0% /run/user/0
tmpfs          tmpfs     791M   36K  791M    1% /run/user/1000
/dev/sdc2      ext4       11G  311M  9.8G    4% /media/zhengyang/userdata
/dev/sdc1      ext4      4.5G  4.4G   35M  100% /media/zhengyang/rootfs

开始制作SD启动卡:

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# sudo dd if=out/rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img of=/dev/sdb bs=4M status=progress

1.2 uboot环境变量

SD卡插入到开发板,并使用准备好的USB转串口适配器和连接线(需另购),连接开发板,给开发板上电。在启动过程中按下CTRL+C进入uboot命令行模式;

点击查看代码
U-Boot 2017.09-g8241716-dirty #root (Dec 17 2023 - 13:44:36 +0800)

Board: NanoPi R6S
PreSerial: 2, raw, 0xfeb50000
DRAM:  16 GiB
Sysmem: init
Relocation Offset: eda2b000
Relocation fdt: eb9f9e20 - eb9fece0
CR: M/C/I
Using default environment

DM: v2
no mmc device at slot 1
mmc@fe2c0000: 1 (SD), mmc@fe2e0000: 0
Bootdev(atags): mmc 1                      # 启动设备为SD卡
MMC1: Legacy, 52Mhz
PartType: EFI
boot mode: normal                          # 进入normal系统 
RESC: 'resource', blk@0x0000a000           # 输出resource分区所在扇区地址
optee api revision: 2.0
TEEC: Waring: Could not find security partition
lib/avb/libavb_user/avb_ops_user.c: trusty_read_lock_state failed
Error determining whether device is unlocked.
Device is: UNLOCKED
DTB: rk3588-nanopi6-rev01.dtb (270114)
HASH(c): OK
ANDROID: fdt overlay OK
I2c0 speed: 100000Hz
vsel-gpios- not found!
en-gpios- not found!
vdd_cpu_big0_s0 800000 uV
vsel-gpios- not found!
en-gpios- not found!
vdd_cpu_big1_s0 800000 uV
I2c2 speed: 100000Hz
vsel-gpios- not found!
en-gpios- not found!
vdd_npu_s0 800000 uV
spi2: RK806: 2
ON=0x00, OFF=0x00
vdd_gpu_s0 750000 uV
vdd_cpu_lit_s0 750000 uV
vdd_log_s0 750000 uV
vdd_vdenc_s0 init 750000 uV
vdd_ddr_s0 850000 uV
get vp0 plane mask:0x7, primary id:0, cursor_plane:2, from dts
get vp1 plane mask:0x148, primary id:6, cursor_plane:3, from dts
get vp2 plane mask:0x280, primary id:7, cursor_plane:9, from dts
Could not find baseparameter partition
I2c6 speed: 200000H
Rockchip UBOOT DRM driver version: v1.0.1
vp0 have layer nr:3[0 1 2 ], primary plane: 0
vp1 have layer nr:3[3 6 8 ], primary plane: 6
vp2 have layer nr:2[7 9 ], primary plane: 7
vp3 have layer nr:0[], primary plane: 0
hdmi@fde80000 disconnected
CLK: (sync kernel. arm: enter 1008000 KHz, init 1008000 KHz, kernel 0N/A)
  b0pll 24000 KHz
  b1pll 24000 KHz
  lpll 24000 KHz
  v0pll 24000 KHz
  aupll 786431 KHz
  cpll 1500000 KHz
  gpll 1188000 KHz
  npll 850000 KHz
  ppll 1100000 KHz
  aclk_center_root 702000 KHz
  pclk_center_root 100000 KHz
  hclk_center_root 396000 KHz
  aclk_center_low_root 500000 KHz
  aclk_top_root 750000 KHz
  pclk_top_root 100000 KHz
  aclk_low_top_root 396000 KHz
vdd_usbc 12520 mV

Net:   No ethernet found.
Hit key to stop autoboot('CTRL+C'):  0
1.2.1 启动命令行

查看内核启动命令;

=> print bootcmd
bootcmd=boot_fit;boot_android ${devtype} ${devnum};bootrkp;run distro_bootcmd;
1.2.2 启动参数

查看内核启动参数:

=> pri bootargs
bootargs=storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal androidboot.dtbo_idx=1
1.2.3 资源文件

uboot查看资源文件:

# 切换到SD卡所属设备
==> mmc dev 1
switch to partitions #0, OK
mmc1 is current device

==> mmc info
Device: mmc@fe2c0000
Manufacturer ID: 3
OEM: 5344
Name: SD32G
Timing Interface: Legacy
Tran Speed: 52000000
Rd Block Len: 512
SD version 3.0
High Capacity: Yes
Capacity: 29.7 GiB
Bus Width: 4-bit
Erase Group Size: 512 Bytes

# 从resource分区读取20个扇区数据
==> mmc read 0x10000000  0xa000 20
MMC read: dev # 1, block # 40960, count 32 ... 32 blocks read: OK

# 查看前两个扇区数据
==> md.b 0x10000000 0x400
10000000: 52 53 43 45 00 00 00 00 01 01 01 00 18 00 00 00    RSCE............
10000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
......
10000200: 45 4e 54 52 72 6b 33 33 39 39 2d 6e 61 6e 6f 70    ENTRrk3399-nanop
10000210: 69 34 2d 72 65 76 30 30 2e 64 74 62 00 00 00 00    i4-rev00.dtb....
10000220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
......
100002e0: 02 8a cd 4f a8 69 32 dd d0 bd de 09 34 59 ad 6e    ...O.i2.....4Y.n
100002f0: 7d 42 d6 ac 00 00 00 00 00 00 00 00 00 00 00 00    }B..............
......

这里我们读取resource分区的数据,也就是resource.img镜像,可以看到以上输出内容中包含了设备树文件的数据。

1.2.4 设备树

查看设备树:

=> print dtb_name
dtb_name=rk3588-nanopi6-rev01.dtb

有关为啥默认使用的该设备树的分析可以参考:《NaniPC-T6设备树》。

1.3 启动内核

当我们在uboot命令行执行了boot命令时,uboot会获取bootcmd环境变量的内容,然后执行bootcmd中保存的启动命令。

接下来我们来分析一下bootcmd默认配置,在默认环境变量default_environment(位于uboot-rockchip/include/env_default.h)中定义有,其内容大致如下:

const uchar default_environment[] = {
	"bootcmd="      CONFIG_BOOTCOMMAND              "\0"
    "bootdelay="    __stringify(CONFIG_BOOTDELAY)    "\0"
    "baudrate="    __stringify(CONFIG_BAUDRATE)    "\0"
    "ipaddr="    __stringify(CONFIG_IPADDR)    "\0"
    "serverip="    __stringify(CONFIG_SERVERIP)    "\0"
    "netmask="    __stringify(CONFIG_NETMASK)    "\0"
    ......
#ifdef  CONFIG_EXTRA_ENV_SETTINGS
        CONFIG_EXTRA_ENV_SETTINGS
#endif
        "\0"
};

默认启动命令CONFIG_BOOTCOMMAND定义在uboot-rockchip/include/configs/nanopi6.h,该文件存放着开发板配置信息,被uboot-rockchip/include/config.h文件引入。

#include <configs/rk3588_common.h>

/* Remove or override few declarations from rk3588-common.h */
#undef CONFIG_BOOTCOMMAND
#undef CONFIG_DISPLAY_BOARDINFO_LATE
#undef RKIMG_DET_BOOTDEV
#undef RKIMG_BOOTCOMMAND

#define CONFIG_SYS_MMC_ENV_DEV          0
#define CONFIG_SYS_MMC_MAX_BLK_COUNT    32768

#define CONFIG_MISC_INIT_R
#define CONFIG_SERIAL_TAG

#ifndef CONFIG_SPL_BUILD

#define ROCKCHIP_DEVICE_SETTINGS \
        "stdout=serial,vidconsole\0" \
        "stderr=serial,vidconsole\0"

#define RKIMG_DET_BOOTDEV \
        "rkimg_bootdev=" \
        "if mmc dev 1 && rkimgtest mmc 1; then " \
                "setenv devtype mmc; setenv devnum 1; echo Boot from SDcard;" \
        "elif mmc dev 0; then " \
                "setenv devtype mmc; setenv devnum 0;" \
        "elif rksfc dev 1; then " \
                "setenv devtype spinor; setenv devnum 1;" \
        "fi; \0"

#define RKIMG_BOOTCOMMAND \
        "boot_fit;" \
        "boot_android ${devtype} ${devnum};" \
        "bootrkp;" \
        "run distro_bootcmd;"

#define CONFIG_BOOTCOMMAND              RKIMG_BOOTCOMMAND

#endif

这里引入了uboot-rockchip/include/configs/rk3588_common.h,而该文件又引入了uboot-rockchip/include/configs/rockchip-common.h

这里支持了内核的4种引导方式:

  • boot_fit:从eMMCboot/recovery分区(如果进入的是normal系统,则为boot分区;如果进入的是recovery系统,则为recovery分区)加载FIT uImage镜像文件(通常由kernel + dtb + ramdisk组成)到内存,然后启动内核 ;
  • boot_android:启动Android内核镜像;
  • bootrkp:通常用于Rockchip平台上的特定启动操作,可能用于启动特定的固件或者特殊的操作模式;
  • distro_bootcmd:运行uboot环境中定义的 distro_bootcmd,这是一个uboot环境变量,通常包含了一系列的启动命令,比如尝试从网络引导、从存储设备引导等;

其中boot_fitdistro_bootcmd启动方式我们在《 Rockchip RK3399 - 移植linux 5.2.8》中有过介绍。

1.3.1 内核启动日志

输入boot命令启动内核:

=> boot
## Booting FIT Image FIT: No fit blob           # 命令boot_fit
FIT: No FIT image
ANDROID: reboot reason: "(none)"                # 命令boot_android
Not AVB images, AVB skip
No valid android hdr
Android image load failed
Android boot failed, error -1.

## Booting Rockchip Format Image                # 命令bootrkp 
fdt      @ 0x08300000 (0x000421b2)              # fdt加载到内存的地址
kernel   @ 0x00400000 (0x021c7808)              # kernel加载到内存的地址
ramdisk  @ 0x0a200000 (0x007b2bc0)              # ramdisk加载到内存的地址
Fdt Ramdisk skip relocation
## Flattened Device Tree blob at 0x08300000
   Booting using the fdt blob at 0x08300000
   Using Device Tree in place at 0000000008300000, end 00000000083451b1
## reserved-memory:
  cma: addr=10000000 size=8000000
  drm-logo@00000000: addr=edf00000 size=468000
  vendor-storage-rm@00000000: addr=ebcd3000 size=10000
  ramoops@110000: addr=110000 size=e0000
Adding bank: 0x00200000 - 0x08400000 (size: 0x08200000)
Adding bank: 0x09400000 - 0xf0000000 (size: 0xe6c00000)
Adding bank: 0x100000000 - 0x3fc000000 (size: 0x2fc000000)
Adding bank: 0x3fc500000 - 0x3fff00000 (size: 0x03a00000)
Adding bank: 0x4f0000000 - 0x500000000 (size: 0x10000000)
Total: 10246.299/11135.828 ms

Starting kernel ...

[   11.146608] Booting Linux on physical CPU 0x0000000000 [0x412fd050]
[   11.146631] Linux version 6.1.25 (root@ubuntu) (aarch64-linux-gnu-gcc (Ubuntu 10.5.0-1ubuntu1~20.04) 10.5.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #1 SMP Wed Dec 27 21:53:18 CST 2023
[   11.153743] Machine model: FriendlyElec NanoPC-T6
......
[   11.510154] Kernel command line: storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal androidboot.dtbo_idx=1 androidboot.verifiedbootstate=orange earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 coherent_pool=1m irqchip.gicv3_pseudo_nmi=0 rw root=/dev/mmcblk0p8 rootfstype=ext4 data=/dev/mmcblk0p9 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
......
Debian GNU/Linux 11 NanoPC-T6 ttyFIQ0

NanoPC-T6 login: [   20.885195] systemd-journald[409]: File /var/log/journal/b9164042f80842f6968af54e1d15c9af/user-1000.journal corrupted or uncleanly shut down, renaming and replacing.
[   21.783657] rk_hdmirx fdee0000.hdmirx-controller: hdmirx_audio_startup: device is no connected or audio is off
[   26.433687] platform mtd_vendor_storage: deferred probe pending

NanoPC-T6 login:

(1) 首先执行boot_fit命令,对于FIT uImage,其中地址范围0x00000000~0x00000027表示的是fdt_header结构体的成员信息。

因此会调用fit_get_blob函数获取boot/recovery分区(如果进入的是normal系统,则获取boot分区;如果进入的是recovery系统,则获取recovery分区)第一个扇区数据,并对fdt_header结构体进行校验判断是不是FIT uImage

由于正常情况下我们进入的是normal系统,则从boot分区加载boot.img数据,从输出的日志信息可以看出我们烧录的boot.img并不是FIT uIamge

(2) 接着执行boot_android命令,从输出日志可以看到应该也是引导失败了。

(3) 执行bootrkp命令。

(4) 执行distro_bootcmd命令。

有关bootrkpdistro_bootcmd启动方式,我们接下来详细介绍。

1.3.2 加载命令行

不知道你有没有留意内核启动输出命令行信息;

[   11.510154] Kernel command line: storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal androidboot.dtbo_idx=1 androidboot.verifiedbootstate=orange earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 coherent_pool=1m irqchip.gicv3_pseudo_nmi=0 rw root=/dev/mmcblk0p8 rootfstype=ext4 data=/dev/mmcblk0p9 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1

这里输出的信息为啥和bootargs环境变量以及arch/arm64/boot/dts/rockchip/rk3588-nanopi6-common.dtsi内容不一样呢?

=> pri bootargs
bootargs=storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal androidboot.dtbo_idx=1


# 设备树设备节点内容
chosen: chosen {
	bootargs = "earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 coherent_pool=1m irqchip.gicv3_pseudo_nmi=0";
};

那么我们不得不介绍内核启动后是如何获取到启动参数。对于ARM64来说,uboot在启动内核时会将r2设置为dtb文件的开始地址。

1.3.2.1 内核bootargs来源

对于NanoPC-T6开发板开发板而言,r2设置为了rk3588-nanopi6-rev01.dtb加载到内存的地址。

这里我们直接从内核start_kernel函数开始说起,其位于init/main.c文件,函数调用栈如下;

#char __initdata boot_command_line[COMMAND_LINE_SIZE];   // 全局变量,定义在init/main.c

start_kernel()  // init/main.c
    char *command_line;	
	.......
	setup_arch(&command_line);  // arch/arm64/kernel/setup.c
		  ......
		  *cmdline_p = boot_command_line;
		  ......
          // __fdt_pointer:dtb所在的物理地址,由bootloader通过x0寄存器传递过来
		  setup_machine_fdt(__fdt_pointer);      // arch/arm64/kernel/setup.c
				// 返回dtb所在的虚拟地址dt_virt
		  		void *dt_virt = fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL)
				if (!dt_virt || !early_init_dt_scan(dt_virt)) {
                    ........
                }
                name = of_flat_dt_get_machine_name();
				pr_info("Machine model: %s\n", name);
	 machine_desc = mdesc;
	......

这里我们重点关注early_init_dt_scan函数,early_init_dt_scan主要是对dtb进行早期的扫描工作,下面是简要介绍函数的调用流程和实现细节:

early_init_dt_scan(dt_virt)                // drivers/of/fdt.c  
    // 对dtb头进行检查
	early_init_dt_verify(dt_virt)
   	early_init_dt_scan_nodes()   // 遍历设备树的节点,解析出重要的信息用于内核启动
        /* Retrieve various information from the /chosen node */
        of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
        /* Initialize {size,address}-cells info */
        of_scan_flat_dt(early_init_dt_scan_root, NULL);
        /* Setup memory, calling early_init_dt_add_memory_arch */
        of_scan_flat_dt(early_init_dt_scan_memory, NULL);

of_scan_flat_dtdtb里面的所有节点进行扫描,用提供的回调函数循环处理节点信息,回调函数返回0继续扫描,返回非0结束扫描,当扫描到最后一个节点也会结束扫描;

/**
 * of_scan_flat_dt - scan flattened tree blob and call callback on each.
 * @it: callback function
 * @data: context data pointer
 *
 * This function is used to scan the flattened device-tree, it is
 * used to extract the memory information at boot before we can
 * unflatten the tree
 */
int __init of_scan_flat_dt(int (*it)(unsigned long node,
                                     const char *uname, int depth,
                                     void *data),
                           void *data)
{
    	//dtb数据的地址,也就是根节点的地址
        const void *blob = initial_boot_params;
        const char *pathp;
        int offset, rc = 0, depth = -1;

        if (!blob)
                return 0;

    	// 从根节点遍历dtb中每个节点,返回的offset就是每个节点的地址
        // offset:表示节点的地址相对于根节点的偏移量,也是节点数据所在地址
        // depth:代表节点相对于根节点的深度,比如根节点深度是0,/chosen节点是1
        for (offset = fdt_next_node(blob, -1, &depth);
             offset >= 0 && depth >= 0 && !rc;
             offset = fdt_next_node(blob, offset, &depth)) {

            	// 解析出节点名称
                pathp = fdt_get_name(blob, offset, NULL);
                if (*pathp == '/')
                        pathp = kbasename(pathp);
            	// 回调函数解析节点,it是传递进来的设备树节点的解析函数,需要解析什么消息就传递进来相应的节点解析函数
                rc = it(offset, pathp, depth, data);
        }
        return rc;
}

early_init_dt_scan_chosen用于扫描chosen节点,并把bootargs属性值拷贝到boot_command_line中,如果内核定义了CONFIG_CMDLINE这个宏,则把配置的命令行参数也拷贝到boot_command_line

/*
 * Convert configs to something easy to use in C code
 */
#if defined(CONFIG_CMDLINE_FORCE)
static const int overwrite_incoming_cmdline = 1;
static const int read_dt_cmdline;
static const int concat_cmdline;
#elif defined(CONFIG_CMDLINE_EXTEND)
static const int overwrite_incoming_cmdline;
static const int read_dt_cmdline = 1;
static const int concat_cmdline = 1;
#else /* CMDLINE_FROM_BOOTLOADER */             // 走这里
static const int overwrite_incoming_cmdline;
static const int read_dt_cmdline = 1;
static const int concat_cmdline;
#endif

#ifdef CONFIG_CMDLINE
static const char *config_cmdline = CONFIG_CMDLINE;
#else
static const char *config_cmdline = "";
#endif


int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
                                     int depth, void *data)
{
        int l = 0;
        const char *p = NULL;
        char *cmdline = data;     // 即boot_command_line
        const void *rng_seed;

        pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);

    	// 节点的深度要为1,数据不能使NULL,同时节点名字是"chosen"或者"chosen@0"
        if (depth != 1 || !cmdline ||
            (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
                return 0;

    	// 解析initrd相关
        early_init_dt_check_for_initrd(node);

        /* Put CONFIG_CMDLINE in if forced or if data had nothing in it to start */
        if (overwrite_incoming_cmdline || !cmdline[0])  // 进入
                strlcpy(cmdline, config_cmdline, COMMAND_LINE_SIZE);

        /* Retrieve command line unless forcing */
        if (read_dt_cmdline)   // 从chosen节点中解析出bootargs属性
                p = of_get_flat_dt_prop(node, "bootargs", &l);

        if (p != NULL && l > 0) {
                if (concat_cmdline) {
                        int cmdline_len;
                        int copy_len;
                        strlcat(cmdline, " ", COMMAND_LINE_SIZE);
                        cmdline_len = strlen(cmdline);
                        copy_len = COMMAND_LINE_SIZE - cmdline_len - 1;
                        copy_len = min((int)l, copy_len);
                        strncpy(cmdline + cmdline_len, p, copy_len);
                        cmdline[cmdline_len + copy_len] = '\0';
                } else {   // 追加bootargs参数到boot_command_line
                        strlcpy(cmdline, p, min((int)l, COMMAND_LINE_SIZE));
                }
        }

        pr_debug("Command line is: %s\n", (char*)data);

        rng_seed = of_get_flat_dt_prop(node, "rng-seed", &l);
        if (rng_seed && l > 0) {
                add_bootloader_randomness(rng_seed, l);

                /* try to clear seed so it won't be found. */
                fdt_nop_property(initial_boot_params, node, "rng-seed");

                /* update CRC check value */
                of_fdt_crc32 = crc32_be(~0, initial_boot_params,
                                fdt_totalsize(initial_boot_params));
        }

        /* break now */
        return 1;
}

如果想查看内核debug级别日志可以配置:

# arch/arm64/configs/nanopi6_linux_defconfig
Kernel hacking  --->
    printk and dmesg options  --->
        (8) Default console loglevel (1-15)   # CONFIG_CONSOLE_LOGLEVEL_DEFAULT
        
# 修改drivers/of/fdt.c  即在需要输出debug级别日志的文件头部定义如下宏
#define DEBUG

通过追加日志,我们重新编译并烧录会发现启动命令行的确是如下这个内容:

[    0.000000] OF: fdt: search "chosen", depth: 1, uname: chosen
[    0.000000] OF: fdt: Looking for initrd properties...
[    0.000000] OF: fdt: initrd_start=0xa200000  initrd_end=0xa9b2bc0
[    0.000000] OF: fdt: Command line is: storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal androidboot.dtbo_idx=1 androidboot.verifiedbootstate=orange earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 coherent_pool=1m irqchip.gicv3_pseudo_nmi=0 rw root=/dev/mmcblk0p8 rootfstype=ext4 data=/dev/mmcblk0p9 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
1.3.2.2 dtbbootargs来源

实际上dtb中的bootargs的来源有如下几种;

  • 内核启动参数bootargs保存在dtschosen节点的bootargs属性,这里就是arch/arm64/boot/dts/rockchip/rk3588-nanopi6-common.dtsi这个设备树源文件;
  • bootargs数据可以是在dts源文件中定义,也可以是uboot启动内核时传递给内核;

其中uboot传递的bootargs参数优先级高于设备树中定义的bootargs,如果是uboot传递的bootargs,在内核启动阶段就会调用fdt_chosen函数将环境变量中的bootargs参数写进dtb数据中;

既然uboot传递了bootargs参数,那么内核将会使用uboot传递过来的bootargs参数,不过该参数为何和内核启动输出的不太一样呢?为此我们不得不去研究bootrkp 启动是否追加了启动参数;

boot_rockchip_image(dev_desc, &part)   // bootrkp启动方式
	......
    // 设置内核加载地址(Image镜像)
	images.ep = kernel_addr_r;
	images.initrd_start = ramdisk_addr_r;
	images.initrd_end = ramdisk_addr_r + ramdisk_size;
	// 设置设备树加载地址
	images.ft_addr = (void *)fdt_addr_r;
    // 设备树长度
	images.ft_len = fdt_totalsize(fdt_addr_r);
	do_bootm_linux(0, 0, NULL, &images);      // arch/arm/lib/bootm.c
		boot_prep_linux(images);              // arch/arm/lib/bootm.c
			image_setup_linux(images)         // common/image.c 
                ulong of_size = images->ft_len;
                char **of_flat_tree = &images->ft_addr;	
				struct lmb *lmb = &images->lmb;
                boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);	
				boot_get_cmdline(lmb, &images->cmdline_start,
                                &images->cmdline_end);
 				boot_relocate_fdt(lmb, of_flat_tree, &of_size);
				image_setup_libfdt(images, *of_flat_tree, of_size, lmb); // common/image-fdt.c
            ......    
        ......
                
# 可以通过如下代码输出启动参数
char *commandline = env_get("bootargs");
printf("%s %d:%s\n", __func__, __LINE__, commandline);

重点关注image_setup_libfdt,定义在common/image-fdt.c

int image_setup_libfdt(bootm_headers_t *images, void *blob,
                       int of_size, struct lmb *lmb)
{
        ulong *initrd_start = &images->initrd_start;
        ulong *initrd_end = &images->initrd_end;
        int ret = -EPERM;
        int fdt_ret;

    	// 进行架构特定的设备树修正
        if (arch_fixup_fdt(blob) < 0) {
                printf("ERROR: arch-specific fdt fixup failed\n");
                goto err;
        }

#if defined(CONFIG_PASS_DEVICE_SERIAL_BY_FDT)     // 定义
    	// 配置根节点
        if (fdt_root(blob) < 0) {
                printf("ERROR: root node setup failed\n");
                goto err;
        }
#endif
    	// 创建/chosen节点
        if (fdt_chosen(blob) < 0) {
                printf("ERROR: /chosen node create failed\n");
                goto err;
        }

        /* Update ethernet nodes */
        fdt_fixup_ethernet(blob);
        if (IMAGE_OF_BOARD_SETUP) {
                fdt_ret = ft_board_setup(blob, gd->bd);
                if (fdt_ret) {
                        printf("ERROR: board-specific fdt fixup failed: %s\n",
                               fdt_strerror(fdt_ret));
                        goto err;
                }
        }
        if (IMAGE_OF_SYSTEM_SETUP) {
                fdt_ret = ft_system_setup(blob, gd->bd);
                if (fdt_ret) {
                        printf("ERROR: system-specific fdt fixup failed: %s\n",
                               fdt_strerror(fdt_ret));
                        goto err;
                }
        }

        /* Delete the old LMB reservation */
        if (lmb)
                lmb_free(lmb, (phys_addr_t)(u32)(uintptr_t)blob,
                         (phys_size_t)fdt_totalsize(blob));

        ret = fdt_shrink_to_minimum(blob, 0);
        if (ret < 0)
                goto err;
        of_size = ret;

        if (*initrd_start && *initrd_end) {
                of_size += FDT_RAMDISK_OVERHEAD;
                fdt_set_totalsize(blob, of_size);
        }
        /* Create a new LMB reservation */
        if (lmb)
                lmb_reserve(lmb, (ulong)blob, of_size);

        fdt_initrd(blob, *initrd_start, *initrd_end);
        if (!ft_verify_fdt(blob))
                goto err;
#if defined(CONFIG_SOC_KEYSTONE)
        if (IMAGE_OF_BOARD_SETUP)
                ft_board_setup_ex(blob, gd->bd);
#endif

        return 0;
err:
        printf(" - must RESET the board to recover.\n\n");

        return ret;
}

这里我们只需要关注fdt_chosen函数,定义在common/fdt_support.c;其中rk3399rk3588 SDKu-boot源码是不一样的;

rk3588为例:

int fdt_chosen(void *fdt)
{
        int   nodeoffset;
        int   err;
        char  *str;             /* used to set string properties */

    	// 检查设备树头部是否有效
        err = fdt_check_header(fdt);
        if (err < 0) {
                printf("fdt_chosen: %s\n", fdt_strerror(err));
                return err;
        }

        /* find or create "/chosen" node.  查找或创建/chosen节点 */
        nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
        if (nodeoffset < 0)
                return nodeoffset;

    	// 获取环境变量bootargs的值
        str = board_fdt_chosen_bootargs(fdt);
        if (str) {
            	// 设置设备树中的bootargs属性
                err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
                                  strlen(str) + 1);
                if (err < 0) {
                        printf("WARNING: could not set bootargs %s.\n",
                               fdt_strerror(err));
                        return err;
                }
        }

        return fdt_fixup_stdout(fdt, nodeoffset);
}

其中board_fdt_chosen_bootargs定义在arch/arm/mach-rockchip/board.c

char *board_fdt_chosen_bootargs(void *fdt)
{
        /* bootargs_ext is used when dtbo is applied. */
        const char *arr_bootargs[] = { "bootargs", "bootargs_ext" };
        const char *bootargs;
        int nodeoffset;
        int i, dump;
        char *msg = "kernel";

        /* debug */
        hotkey_run(HK_INITCALL);
        dump = is_hotkey(HK_CMDLINE);
        if (dump)
                printf("## bootargs(u-boot): %s\n\n", env_get("bootargs"));

        /* find or create "/chosen" node. */
        nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
        if (nodeoffset < 0)
                return NULL;

    	// 遍历arr_bootargs,检查设备树中是否已有相关的bootargs
        for (i = 0; i < ARRAY_SIZE(arr_bootargs); i++) {
            	// 获取/chosen节点的bootargs、bootargs_ext属性值
                bootargs = fdt_getprop(fdt, nodeoffset, arr_bootargs[i], NULL);
                if (!bootargs)
                        continue;
                if (dump)
                        printf("## bootargs(%s-%s): %s\n\n",
                               msg, arr_bootargs[i], bootargs);
                /*
                 * Append kernel bootargs
                 * If use AB system, delete default "root=" which route
                 * to rootfs. Then the ab bootctl will choose the
                 * high priority system to boot and add its UUID
                 * to cmdline. The format is "roo=PARTUUID=xxxx...".
                 */
#ifdef CONFIG_ANDROID_AB
                env_update_filter("bootargs", bootargs, "root=");
#else
            	// 进入,更新bootargs环境变量,追加设备树中配置的bootargs	
                env_update("bootargs", bootargs);
#endif
        }

#ifdef CONFIG_VENDOR_FRIENDLYELEC // 针对FriendlyELEC板卡的处理,进入
        char *panel = board_get_panel_name();
    	// 如果设置了panel,更新bootargs环境变量,比如追加lcd=HD702E,213dpi
        if (panel) {
                char lcdinfo[128] = { 0 };
                strcpy(lcdinfo, "lcd=");
                strncat(lcdinfo, panel, sizeof(lcdinfo) - 5);
                env_update("bootargs", lcdinfo);
        }
#endif
#if defined(CONFIG_ENVF) || defined(CONFIG_ENV_PARTITION)  
        ......
#endif

#ifdef CONFIG_MTD_BLK
        ......
#endif

#ifdef CONFIG_ANDROID_AB
        ab_update_root_partition();
#endif
        /*
         * Initrd fixup: remove unused "initrd=0x...,0x...",
         * this for compatible with legacy parameter.txt
         */
        env_delete("bootargs", "initrd=", 0);

        /*
         * If uart is required to be disabled during
         * power on, it would be not initialized by
         * any pre-loader and U-Boot.
         *
         * If we don't remove earlycon from commandline,
         * kernel hangs while using earlycon to putc/getc
         * which may dead loop for waiting uart status.
         * (It seems the root cause is baundrate is not
         * initilalized)
         *
         * So let's remove earlycon from commandline.
         */
        if (gd->flags & GD_FLG_DISABLE_CONSOLE)
                env_delete("bootargs", "earlycon=", 0);

        /* Android header v4+ need this handle */
#ifdef CONFIG_ANDROID_BOOT_IMAGE
        struct andr_img_hdr *hdr;

        hdr = (void *)env_get_ulong("android_addr_r", 16, 0);
        if (hdr && !android_image_check_header(hdr) && hdr->header_version >= 4) {
                if (env_update_extract_subset("bootargs", "andr_bootargs", "androidboot."))
                        printf("extract androidboot.xxx error\n");
                if (dump)
                        printf("## bootargs(android): %s\n\n", env_get("andr_bootargs"));
        }
#endif
        bootargs = env_get("bootargs");
        if (dump)
                printf("## bootargs(merged): %s\n\n", bootargs);

        return (char *)bootargs;
}

rk3399为例:

int fdt_chosen(void *fdt)
{
        /*
         * "bootargs_ext" is used when dtbo is applied.
         */
        const char *arr_bootargs[] = { "bootargs", "bootargs_ext" };
        int   nodeoffset;
        int   err;
        int   i;
        char  *str;             /* used to set string properties */
        int dump;

    	// 判断HK_CMDLINE是否是热键,返回false
        dump = is_hotkey(HK_CMDLINE);

    	// 检查设备树头部是否有效
        err = fdt_check_header(fdt);
        if (err < 0) {
                printf("fdt_chosen: %s\n", fdt_strerror(err));
                return err;
        }

        /* find or create "/chosen" node.  查找或创建/chosen节点 */
        nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
        if (nodeoffset < 0)
                return nodeoffset;

    	// 获取环境变量bootargs的值
        str = env_get("bootargs");
        if (str) {  // 如果环境变量配置了bootargs
#ifdef CONFIG_ARCH_ROCKCHIP       // 针对Rockchip架构的处理
                const char *bootargs;

                if (dump)
                        printf("## U-Boot bootargs: %s\n", str);

            	// 遍历arr_bootargs,检查设备树中是否已有相关的bootargs
                for (i = 0; i < ARRAY_SIZE(arr_bootargs); i++) {
                    	// 获取/chosen节点的bootargs、bootargs_ext属性值
                        bootargs = fdt_getprop(fdt, nodeoffset,
                                               arr_bootargs[i], NULL);
                    	// 1. fdt_chosen 389:earlycon=uart8250,mmio32,0xff1a0000 swiotlb=1 coherent_pool=1m
                    	// 2. fdt_chosen 389:root=/dev/mmcblk2p8 rw rootfstype=ext4 rootflags=discard data=/dev/mmcblk2p9 console=ttyFIQ0 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
						printf("%s %d:%s\n", __func__, __LINE__, bootargs);
                        // 如果存在,更新环境变量bootargs
                        if (bootargs) {
                                if (dump)
                                        printf("## Kernel %s: %s\n",
                                               arr_bootargs[i], bootargs);
                                /*
                                 * Append kernel bootargs
                                 * If use AB system, delete default "root=" which route
                                 * to rootfs. Then the ab bootctl will choose the
                                 * high priority system to boot and add its UUID
                                 * to cmdline. The format is "roo=PARTUUID=xxxx...".
                                 */
                                hotkey_run(HK_INITCALL);
#ifdef CONFIG_ANDROID_AB       // 未定义
                                env_update_filter("bootargs", bootargs, "root=");
#else                           // 进入,更新bootargs环境变量,追加设备树中配置的bootargs
                                env_update("bootargs", bootargs);
#endif
#ifdef CONFIG_MTD_BLK          // 未定义
                                char *mtd_par_info = mtd_part_parse();

                                if (mtd_par_info) {
                                        if (memcmp(env_get("devtype"), "mtd", 3) == 0)
                                                env_update("bootargs", mtd_par_info);
                                }
#endif
                                /*
                                 * Initrd fixup: remove unused "initrd=0x...,0x...",
                                 * this for compatible with legacy parameter.txt
                                 */
                                env_delete("bootargs", "initrd=", 0);

                                /*
                                 * If uart is required to be disabled during
                                 * power on, it would be not initialized by
                                 * any pre-loader and U-Boot.
                                 *
                                 * If we don't remove earlycon from commandline,
                                 * kernel hangs while using earlycon to putc/getc
                                 * which may dead loop for waiting uart status.
                                 * (It seems the root cause is baundrate is not
                                 * initilalized)
                                 *
                                 * So let's remove earlycon from commandline.
                                 */
                                if (gd->flags & GD_FLG_DISABLE_CONSOLE)
                                        env_delete("bootargs", "earlycon=", 0);
                        }
                }
#endif

#ifdef CONFIG_VENDOR_FRIENDLYELEC        // 针对FriendlyELEC板卡的处理,进入
                char *panel = board_get_panel_name();
            	// 如果设置了panel,更新bootargs环境变量,比如追加lcd=HD702E,213dpi
                if (panel) {
                        char lcdinfo[128] = { 0 };
                        strcpy(lcdinfo, "lcd=");
                        strncat(lcdinfo, panel, sizeof(lcdinfo) - 5);
                        env_update("bootargs", lcdinfo);
                }
#endif
				// 获取更新后的bootargs环境变量,并设置设备树中的bootargs属性
                str = env_get("bootargs");
            	// fdt_chosen 451:storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal androidboot.dtbo_idx=0 earlycon=uart8250,mmio32,0xff1a0000 swiotlb=1 coherent_pool=1m rw root=/dev/mmcblk2p8 rootfstype=ext4 rootflags=discard data=/dev/mmcblk2p9 console=ttyFIQ0 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1 lcd=HD702E,213dpi
                printf("%s %d:%s\n", __func__, __LINE__, str);
                err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
                                  strlen(str) + 1);
                if (err < 0) {
                        printf("WARNING: could not set bootargs %s.\n",
                               fdt_strerror(err));
                        return err;
                }
        }

        if (dump)
                printf("## Merged bootargs: %s\n", env_get("bootargs"));

        return fdt_fixup_stdout(fdt, nodeoffset);
}

通过分析,可以了解到fdt_chosen 函数主要完成了以下任务:

  • 确保设备树的 /chosen 节点存在;
  • 从环境变量中获取和处理启动参数 bootargs
  • 根据不同的硬件配置(如Rockchip架构或FriendlyELEC板卡)调整启动参数;
  • 更新设备树中的 bootargs 属性,确保内核可以正确获得启动参数;
  • 修正标准输出设备配置。

在上面代码执行过程中我们输出了/chosen节点的bootargsbootargs_ext属性值,其中bootargs_ext属性值哪里来的呢?

root=/dev/mmcblk2p8 rw rootfstype=ext4 rootflags=discard data=/dev/mmcblk2p9 console=ttyFIQ0 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1

这个值实际上配置在dtbo.img镜像中,具体可以参考android_fdt_overlay_apply函数,这个我们在接下来的内容会介绍到。

有关内核系统的更多细节可以参考:

1.4 uboot编译和烧录

1.4.1 编译

如果我们对uboot源码有改动,执行如下命令进行编译;

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588# UBOOT_SRC=$PWD/uboot-rockchip ./build-uboot.sh debian-bullseye-desktop-arm64

编译完成后debian-bullseye-desktop-arm64目录下的uboot.img被更新了。

注意:编译之前需要安装交叉编译工具以及配置build-uboot.sh脚本,具体参考《Rockchip RK3588 - 移植uboot 2017.09 & linux 6.1(友善之家脚本方式)》。

1.4.2 dd烧录

由于uboot.img占用的分区是uboot分区,假设SD/TF Card设备节点为/dev/mmcblk0

我们在ubuntu开启http下载服务,或者使用scp将镜像文件发送到开发版;

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588/debian-bullseye-desktop-arm64$ python3 -m http.server 8080

开发板下载uboot.img,然后使用如下命令烧录;

pi@NanoPC-T6:/opt# sudo wget 192.168.0.200:8080/uboot.img
pi@NanoPC-T6:/opt# sudo dd if=uboot.img of=/dev/mmcblk0p1 bs=1M

同样如果我们修改了resource.img,也可以使用如下命令烧录;

pi@NanoPC-T6:/opt# sudo wget 192.168.0.200:8080/resource.img
pi@NanoPC-T6:/opt# sudo dd if=resource.img of=/dev/mmcblk0p4 bs=1M

1.5 补充

我们在阅读uboot源代码的时候,判断一个宏有没有定义,我们重点关注以下两个文件;

(1) uboot-rockchip/include/generated/autoconf.h

  • 这个文件是由uboot的配置系统自动生成的。在uboot编译过程中,会根据你选择的配置选项(通过make menuconfig或者手动修改config文件)来生成不同的配置头文件。这些选项包括处理器架构、设备支持、功能选项等;
  • autoconf.h文件包含了一系列的宏定义,用来指示编译系统当前的配置状态。这些宏定义通常用于条件编译(如#ifdef#ifndef)以及在代码中选择性地包含或排除特定的功能和配置。

(2) uboot-rockchip/include/configs/nanopi6.h

这个文件是一个特定开发板或者硬件平台的配置文件,每个支持的硬件平台或开发板在uboot源码中通常都有一个相应的配置头文件,存储在include/configs/目录下。

nanopi6.h它包含了关于该开发板的具体配置选项,包括存储设备(如NAND FlashSD 卡)的初始化配置、默认的启动命令等。这些信息是针对特定硬件的,确保uboot能够正确地操作和引导该硬件平台。

二、bootrkp

bootrkp命令的实现位于uboot-rockchip/cmd/bootrkp.cuboot中使用宏U_BOOT_CMD来定义命令;

U_BOOT_CMD(
        bootrkp,  1,     1,      do_boot_rockchip,
        "Boot Linux Image from rockchip image type",
        "kernel.img: zImage/Image\n"
        "boot.img: ramdisk\n"
        "resource.img: dtb, u-boot logo, kernel logo"
);

该宏展开后实际上是一个cmd_tbl_t结构体变量,命令名为bootrkp,命令处理函数为do_boot_rockchip

static int boot_rockchip_image(struct blk_desc *dev_desc,
                               disk_partition_t *boot_part)
{
        disk_partition_t kernel_part;
        ulong ramdisk_addr_r;
        ulong kernel_addr_r;
        ulong fdt_addr_r;
        int ramdisk_size;
        int kernel_size;
        int fdt_size;
        int ret;

        printf("\n## Booting Rockchip Format Image\n");

        // 从环境变量获取ramdisk_addr_r地址  环境变量默认配置的ramdisk_addr_r=0x0a200000
        ramdisk_addr_r = env_get_ulong("ramdisk_addr_r", 16, 0);
        // 从环境变量获取kernel_addr_r地址  环境变量默认配置的kernel_addr_r=0x00400000
        kernel_addr_r = env_get_ulong("kernel_addr_r", 16, 0);
        // 从环境变量获取fdt_addr_r地址  环境变量默认配置的fdt_addr_r=0x08300000
        fdt_addr_r = env_get_ulong("fdt_addr_r", 16, 0);

    	// 根据分区名获取分区信息, PART_KERNEL="kernel"  
    	// 分区名称是写在gpt分区表中的,开始扇区0x12000,占用扇区数量0x00014000 扇区大小40MB
        ret = part_get_info_by_name(dev_desc, PART_KERNEL, &kernel_part);
        if (ret < 0) {
                printf("Could not find kernel partition, ret=%d\n", ret);
                return -EINVAL;
        }
		
    	// 从块设备(dev_desc)读取kernel分区数据到内存地址kernel_addr_r
        kernel_size = read_rockchip_image(dev_desc, &kernel_part,
                                          (void *)kernel_addr_r);
        if (kernel_size < 0) {
                printf("Failed to read kernel image, ret=%d\n", ret);
                return -EINVAL;
        }

    	// 从块设备(dev_desc)读取boot_part分区数据到内存地址ramdisk_addr_r
        ramdisk_size = read_rockchip_image(dev_desc, boot_part,
                                           (void *)ramdisk_addr_r);
        if (ramdisk_size < 0)
                ramdisk_size = 0;

    	// 检查全局数据结构gd中的fdt_blob是否等于fdt_addr_r。通常情况下,fdt_blob是设备树的起始地址
        if (gd->fdt_blob != (void *)fdt_addr_r) {
                fdt_size = rockchip_read_dtb_file((void *)fdt_addr_r);
                if (fdt_size < 0) {
                        printf("Failed to read fdt, ret=%d\n", fdt_size);
                        return -EINVAL;
                }
        }

        env_set("bootm-no-reloc", "y");

        printf("fdt      @ 0x%08lx (0x%08x)\n", fdt_addr_r, fdt_totalsize(fdt_addr_r));
        printf("kernel   @ 0x%08lx (0x%08x)\n", kernel_addr_r, kernel_size);
        printf("ramdisk  @ 0x%08lx (0x%08x)\n", ramdisk_addr_r, ramdisk_size);

#if defined(CONFIG_ARM64)
        char cmdbuf[64];

    	// 执行booti命令,启动Image格式内核镜像
    	// 参数依次为 内核镜像在内存中地址 ramdisk文件系统在内存中的地址:大小 设备树在内存中的地址
        snprintf(cmdbuf, 64, "booti 0x%lx 0x%lx:0x%x 0x%lx",
                 kernel_addr_r, ramdisk_addr_r, ramdisk_size, fdt_addr_r);
        run_command(cmdbuf, 0);
#else
        /* We asume it's always zImage on 32-bit platform */
        ulong kaddr_c = env_get_ulong("kernel_addr_c", 16, 0);
        ulong kaddr_r, kaddr, ksize;

        if (kernel_addr_r && !kaddr_c) {
                kaddr_c = kernel_addr_r;
                kaddr_r = CONFIG_SYS_SDRAM_BASE;
        }

        if (!sysmem_free((phys_addr_t)kaddr_c)) {
                kaddr = kaddr_r;
                ksize = kernel_size * 100 / 45 ; /* Ratio: 45% */
                ksize = ALIGN(ksize, dev_desc->blksz);
                if (!sysmem_alloc_base(MEM_UNCOMP_KERNEL,
                                       (phys_addr_t)kaddr, ksize))
                        return -ENOMEM;
        }

        boot_lmb_init(&images);
        images.ep = kernel_addr_r;
        images.initrd_start = ramdisk_addr_r;
        images.initrd_end = ramdisk_addr_r + ramdisk_size;
        images.ft_addr = (void *)fdt_addr_r;
        images.ft_len = fdt_totalsize(fdt_addr_r);
        do_bootm_linux(0, 0, NULL, &images);
#endif

        return 0;
}

static int do_boot_rockchip(cmd_tbl_t *cmdtp, int flag,
                            int argc, char *const argv[])
{
        char *part_name = PART_BOOT;
        struct blk_desc *dev_desc;
        disk_partition_t part;
        int ret;
		// 获取启动设备
        dev_desc = rockchip_get_bootdev();
        if (!dev_desc) {
                printf("dev_desc is NULL!\n");
                return CMD_RET_FAILURE;
        }

#ifdef CONFIG_ANDROID_KEYMASTER_CA               // 配置了
        /* load attestation key from misc partition. */
        ret = part_get_info_by_name(dev_desc, PART_MISC, &part);
        if (ret < 0)
                printf("Could not find misc partition\n");
        else
                load_attestation_key(dev_desc, &part);
#endif

#ifdef CONFIG_FASTBOOT_OEM_UNLOCK   // 未配置
        ......
#endif
        // 启动进入recovery系统? 如果是,则从recovery分区加载ramdisk文件系统
        if (rockchip_get_boot_mode() == BOOT_MODE_RECOVERY)
                part_name = PART_RECOVERY;

    	// 根据分区名获取分区信息 part_name="boot",如果是进入recovery系统,part_name=“从recovery”
        ret = part_get_info_by_name(dev_desc, part_name, &part);
        if (ret < 0) {
                printf("Could not find %s part\n", part.name);
                return CMD_RET_FAILURE;
        }

        return boot_rockchip_image(dev_desc, &part) ? CMD_RET_FAILURE : 0;
}

2.1 ENV_MEM_LAYOUT_SETTINGS

首先是获取ramdisk_addr_rkernel_addr_rfdt_addr_r环境的值。这些环境变量默认值定义在uboot-rockchip/include/configs/rk3588_common.h

#define PARTS_RKIMG \                   // 这个是定义在include/configs/rockchip-common.h
        "uuid_disk=${uuid_gpt_disk};" \
        "name=uboot,start=8MB,size=4MB,uuid=${uuid_gpt_loader2};" \
        "name=trust,size=4M,uuid=${uuid_gpt_atf};" \
        "name=misc,size=4MB,uuid=${uuid_gpt_misc};" \
        "name=resource,size=16MB,uuid=${uuid_gpt_resource};" \
        "name=kernel,size=32M,uuid=${uuid_gpt_kernel};" \
        "name=boot,size=32M,bootable,uuid=${uuid_gpt_boot};" \
        "name=recovery,size=32M,uuid=${uuid_gpt_recovery};" \
        "name=backup,size=112M,uuid=${uuid_gpt_backup};" \
        "name=cache,size=512M,uuid=${uuid_gpt_cache};" \
        "name=system,size=2048M,uuid=${uuid_gpt_system};" \
        "name=metadata,size=16M,uuid=${uuid_gpt_metadata};" \
        "name=vendor,size=32M,uuid=${uuid_gpt_vendor};" \
        "name=oem,size=32M,uuid=${uuid_gpt_oem};" \
        "name=frp,size=512K,uuid=${uuid_gpt_frp};" \
        "name=security,size=2M,uuid=${uuid_gpt_security};" \
        "name=userdata,size=-,uuid=${uuid_gpt_userdata};\0"

/*
 * decompressed kernel:  4M ~ 84M
 *      Why not start from 2M ? if kernel < 5.10 in Android image,
 *      the image header will use the 0x180000~0x200000, which is
 *      overlap with share memory region 0x100000~0x200000.
 *
 * compressed kernel:   84M ~ 130M
 */
#define ENV_MEM_LAYOUT_SETTINGS \
        "scriptaddr=0x00500000\0" \
        "pxefile_addr_r=0x00600000\0" \
        "fdt_addr_r=0x08300000\0" \
        "kernel_addr_r=0x00400000\0" \
        "kernel_addr_c=0x05480000\0" \
        "ramdisk_addr_r=0x0a200000\0"
        
#define CONFIG_EXTRA_ENV_SETTINGS \
        BOOTENV_SHARED_MTD      \
        ENV_MEM_LAYOUT_SETTINGS \            // 加载地址
        "partitions=" PARTS_RKIMG \          // 分区信息
        ROCKCHIP_DEVICE_SETTINGS \
        RKIMG_DET_BOOTDEV \
        BOOTENV
#endif

这里通过CONFIG_EXTRA_ENV_SETTINGS定义了额外的环境变量。我们可以在uboot命令行查看这些环境变量;

=> pri kernel_addr_r
kernel_addr_r=0x00400000
=> pri kernel_addr_c
kernel_addr_c=0x05480000
=> pri ramdisk_addr_r
ramdisk_addr_r=0x0a200000
=> pri partitions
partitions=uuid_disk=${uuid_gpt_disk};name=uboot,start=8MB,size=4MB,uuid=${uuid_gpt_loader2};name=trust,size=4M,uuid=${uuid_gpt_atf};name=misc,size=4MB,uuid=${uuid_gpt_misc};name=resource,size=16MB,uuid=${uuid_gpt_resource};name=kernel,size=32M,uuid=${uuid_gpt_kernel};name=boot,size=32M,bootable,uuid=${uuid_gpt_boot};name=recovery,size=32M,uuid=${uuid_gpt_recovery};name=backup,size=112M,uuid=${uuid_gpt_backup};name=cache,size=512M,uuid=${uuid_gpt_cache};name=system,size=2048M,uuid=${uuid_gpt_system};name=metadata,size=16M,uuid=${uuid_gpt_metadata};name=vendor,size=32M,uuid=${uuid_gpt_vendor};name=oem,size=32M,uuid=${uuid_gpt_oem};name=frp,size=512K,uuid=${uuid_gpt_frp};name=security,size=2M,uuid=${uuid_gpt_security};name=userdata,size=-,uuid=${uuid_gpt_userdata};

2.2 read_rockchip_image

read_rockchip_image函数从块设备dev_desc读取分区part数据到内存地址dst,并返回读取的数据长度;

/*
 * non-OTA packaged kernel.img & boot.img return the image size on success,
 * and a negative value on error.
 */
static int read_rockchip_image(struct blk_desc *dev_desc,
                               disk_partition_t *part, void *dst)
{
        struct rockchip_image *img = dst;
        int header_len = 8;
        int cnt, ret;
#ifdef CONFIG_ROCKCHIP_CRC
        u32 crc32;
#endif
    	// 将part所指定分区第一个块内容加载到内存地址img
        ret = blk_dread(dev_desc, part->start, 1, img);
        if (ret != 1)
                return -EIO;
    
		// 判断镜像头标签是否是TAG_KERNEL include/boot_rkimg.h:50: #define TAG_KERNEL                      0x4C4E524B
        if (img->tag != TAG_KERNEL) {
                printf("Invalid %s image tag(0x%x)\n", part->name, img->tag);
                return -EINVAL;
        }

        /* total size = image size + 8 bytes header + 4 bytes crc32 */
        // 除法运算,向上取整, 计算镜像实际占用的扇区,每个扇区大小512
        cnt = DIV_ROUND_UP(img->size + 8 + 4, RK_BLK_SIZE);
        if (!sysmem_alloc_base_by_name((const char *)part->name,
                                       (phys_addr_t)dst,
                                       cnt * dev_desc->blksz))
                return -ENXIO;
		
    	// 从块设备dev_desc的part->start扇区开始读取cnt个扇区的数据,并将数据存储在dst指向的内存中。
        ret = blk_dread(dev_desc, part->start, cnt, dst);
        if (ret != cnt) {
                printf("Failed to read %s part, blkcnt=%d\n", part->name, ret);
                return -EIO;
        } else {
                ret = img->size;
        }
    	// dst+header_len处复制img->size字节的数据到dst处
        memcpy(dst, dst + header_len, img->size);

#ifdef CONFIG_ROCKCHIP_CRC
        printf("%s image rk crc32 verify... ", part->name);
        crc32 = crc32_verify((uchar *)(ulong)dst, img->size + 4);
        if (!crc32) {
                printf("fail!\n");
                ret = -EINVAL;
        } else {
                printf("okay.\n");
        }
#endif
        return ret;
}

这里我们可以以二进制方式打开我们编译的统一固件kernel.img

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588/debian-bullseye-desktop-arm64$ xxd -b -e -l 16 kernel.img
00000000: 4c4e524b 021c7808 d503201f 145d5f43  KRNL.x... ..C_].

们以大端方式(数据的低位字节存储在高地址,高位字节存储在低地址)来解析,解析到的rockchip_image内容如下:

struct rockchip_image {     // uboot-rockchip/include/boot_rkimg.h
        uint32_t tag;      // 4个字节      0x4c4e524b
        uint32_t size;     // 4个字节      0x021c7808  => 35420168
        int8_t image[1];   // 动态定义数组长度,它用来存储实际的镜像数据
        uint32_t crc;      // 4个字节  
};

查看内核镜像实际大小为35420180,去除掉12个字节后就是35420168

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588/uboot-rockchip/debian-bullseye-desktop-arm64$ ll kernel.img      -rw-r--r-- 1 root root 35420180 12月 27  2023 kernel.img

2.3 rockchip_read_dtb_file

rockchip_read_dtb_file函数定义在uboot-rockchip/arch/arm/mach-rockchip/boot_rkimg.c,在resource.img中搜索rk3588-nanopi6-rev01.dtb

找到之后把它加载到 fdt_addr_r. 最后更新gd->fdt_blob = (void *)fdt_addr

enum {
        LOCATE_DISTRO,
        LOCATE_RESOURCE,
        LOCATE_FIT,
        LOCATE_END,
};

int rockchip_read_dtb_file(void *fdt)
{
        int locate, ret;
        int size;

    	// 依次从LOCATE_DISTRO、LOCATE_RESOURCE、LOCATE_FIT处加载设备树,只要有一次加载成功 则跳出循环
        for (locate = 0; locate < LOCATE_END; locate++) {            
                ret = dtb_scan(fdt, locate);
                if (!ret)
                        break;
        }
        if (ret) {
                printf("No valid DTB, ret=%d\n", ret);
                return ret;
        }

        /* reserved memory,更新大小  */
        size = fdt_totalsize(fdt);
        if (!sysmem_alloc_base(MEM_FDT, (phys_addr_t)fdt,
                ALIGN(size, RK_BLK_SIZE) + CONFIG_SYS_FDT_PAD))
                return -ENOMEM;

        /* fixup/overlay */
        rk_board_early_fdt_fixup(fdt);
#if defined(CONFIG_ANDROID_BOOT_IMAGE) && defined(CONFIG_OF_LIBFDT_OVERLAY)  // 进入
        android_fdt_overlay_apply((void *)fdt);
#endif

        return 0;
}

函数调用链如下:

rockchip_read_dtb_file(void *fdt) -->
	dtb_scan(fdt, locate) -->
		rockchip_read_resource_dtb(fdt_addr, &hash, &hash_size); -->	// 在resource.img中查找rk3588-nanopi6-rev01.dtb,将rk3588-nanopi6-rev01.dtb读取到fdt_addr
			f = resource_get_file(dtb_name); -->  // 在resource.img中搜索dtb_name(值为rk3588-nanopi6-rev01.dtb,环境变量中定义)
				resource_scan(); // 用于从某个分区(可能是boot/recovery/resource中任意一种)获取资源文件,并将文件信息添加到链表entry_head;
    				fit_image_init_resource(desc);
					resource_default(desc, &part, &blk_offset); // // 获取resource分区信息(resource.img)
					resource_init(desc, &part, blk_offset);
						android_image_check_header((void *)hdr);
						resource_check_header((void *)hdr);
                        android_image_verify_resource((const char *)part->name, &resc_buf);
						// 读取resource分区获取所有entry项,并添加到链表entry_head
						resource_setup_blk_list(desc, part->start + blk_offset);
            rockchip_read_resource_file(fdt_addr, f->name, 0, 0); // 将rk3588-nanopi6-rev01.dtb加载到ftd_addr这个地址.
		// 指定的设备树读取失败执行
		printf("Failed to load DTB, ret=%d\n", ret);	
	// 如果指定的设备树读取失败执行
	printf("No valid DTB, ret=%d\n", ret); 
	rk_board_early_fdt_fixup(fdt);
    android_fdt_overlay_apply((void *)fdt);

这里我们需要提一下rockchip_read_dtb_file函数第一次被调用是在board_init函数;

board_init() -->
    init_kernel_dtb() -->
        rockchip_read_dtb_file((void *)fdt_addr); 
		// 指定的设备树读取失败执行
		printf("Failed to get kernel dtb, ret=%d\n", ret);

如果定义了CONFIG_USING_KERNEL_DTB就会调用init_kernel_dtb函数,函数位于arch/arm/mach-rockchip/kernel_dtb.c

int init_kernel_dtb(void)
{
#ifndef CONFIG_USING_KERNEL_DTB_V2
        void *ufdt_blob = (void *)gd->fdt_blob;
#endif
        ulong fdt_addr = 0;
        int ret = -ENODEV;

        printf("DM: v%d\n", IS_ENABLED(CONFIG_USING_KERNEL_DTB_V2) ? 2 : 1);

        /*
         * If memory size <= 128MB, we firstly try to get "fdt_addr1_r".
         */
        if (gd->ram_size <= SZ_128M)
                fdt_addr = env_get_ulong("fdt_addr1_r", 16, 0);

        if (!fdt_addr)
                fdt_addr = env_get_ulong("fdt_addr_r", 16, 0);
        if (!fdt_addr) {
                printf("No Found FDT Load Address.\n");
                return -ENODEV;
        }

#ifdef CONFIG_EMBED_KERNEL_DTB_ALWAYS
        printf("Always embed kernel dtb\n");
        goto dtb_embed;
#endif
        // 执行
        ret = rockchip_read_dtb_file((void *)fdt_addr);
        if (!ret) {
                if (!dtb_check_ok((void *)fdt_addr, (void *)gd->fdt_blob)) {
                        ret = -EINVAL;
                        printf("Kernel dtb mismatch this platform!\n");
                } else {
                        goto dtb_okay;
                }
        }

#ifdef CONFIG_EMBED_KERNEL_DTB
#ifdef CONFIG_EMBED_KERNEL_DTB_ALWAYS
dtb_embed:
#endif
        if (gd->fdt_blob_kern) {
                if (!dtb_check_ok((void *)gd->fdt_blob_kern, (void *)gd->fdt_blob)) {
                        printf("Embedded kernel dtb mismatch this platform!\n");
                        return -EINVAL;
                }

                fdt_addr = (ulong)memalign(ARCH_DMA_MINALIGN,
                                fdt_totalsize(gd->fdt_blob_kern));
                if (!fdt_addr)
                        return -ENOMEM;

                /*
                 * Alloc another space for this embed kernel dtb.
                 * Because "fdt_addr_r" *MUST* be the fdt passed to kernel.
                 */
                memcpy((void *)fdt_addr, gd->fdt_blob_kern,
                       fdt_totalsize(gd->fdt_blob_kern));
                printf("DTB: %s\n", CONFIG_EMBED_KERNEL_DTB_PATH);
        } else
#endif
        {
                printf("Failed to get kernel dtb, ret=%d\n", ret);
                return -ENOENT;
        }

dtb_okay:
        gd->fdt_blob = (void *)fdt_addr;
        hotkey_run(HK_FDT);

#ifndef CONFIG_USING_KERNEL_DTB_V2
        /*
         * There is a phandle miss match between U-Boot and kernel dtb node,
         * we fixup it in U-Boot live dt nodes.
         *
         * CRU:  all nodes.
         * GPIO: key nodes.
         */
        phandles_fixup_cru((void *)gd->fdt_blob);
        phandles_fixup_gpio((void *)gd->fdt_blob, (void *)ufdt_blob);
#endif

        gd->flags |= GD_FLG_KDTB_READY;
        gd->of_root_f = gd->of_root;
        of_live_build((void *)gd->fdt_blob, (struct device_node **)&gd->of_root);
        dm_scan_fdt((void *)gd->fdt_blob, false);

#ifdef CONFIG_USING_KERNEL_DTB_V2
        dm_rm_kernel_dev();
        dm_rm_u_boot_dev();
#endif
        /*
         * There maybe something for the mmc devices to do after kernel dtb
         * dm setup, eg: regain the clock device binding from kernel dtb.
         */
        mmc_dm_reinit();

        /* Reserve 'reserved-memory' */
        ret = boot_fdt_add_sysmem_rsv_regions((void *)gd->fdt_blob);
        if (ret)
                return ret;

        return 0;
}
2.3.1 resource.img

由于是从resource.img中搜索rk3588-nanopi6-rev01.dtb,因此我们有必要了解一下该镜像文件中各种资源文件的存放格式。

我们在《Rockchip RK3588 - 移植uboot 2017.09 & linux 6.1(友善之家脚本方式)》中介绍过resource.img是通过resource_tool 工具生成的;

scripts/resource_tool ${DTB_ARGS} ${DTB_PATH} ${LOGO} ${LOGO_KERNEL} >/dev/null
# 替换参数
scripts/resource_tool --dtbname  \
	./arch/arm64/boot/dts/rockchip/rk3399-nanopi4-rev00.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3399-nanopi4-rev01.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3399-nanopi4-rev04.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3399-nanopi4-rev06.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3399-nanopi4-rev07.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3399-nanopi4-rev09.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3399-nanopi4-rev0a.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3399-nanopi4-rev0b.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3399-nanopi4-rev21.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3399-nanopi4-rev22.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3568-nanopi5-rev01.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3568-nanopi5-rev02.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3568-nanopi5-rev03.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3568-nanopi5-rev04.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3568-nanopi5-rev07.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3588-nanopi6-rev01.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3588-nanopi6-rev02.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3588-nanopi6-rev03.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3588-nanopi6-rev04.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3588-nanopi6-rev05.dtb  \
	./arch/arm64/boot/dts/rockchip/rk3588-nanopi6-rev09.dtb  \
	logo.bmp logo_kernel.bmp

因此可以解析resource.img并从中获取到rk3588-nanopi6-rev01.dtb文件信息,resource.img文件格式如下;

 * ---------------------------------------------- 
 * |    header  (1 block)                       | 头
 * ---------------------------------------------| 
 * |    entry0  (1 block) |                     | rk3399-nanopi4-rev00.dtb文件描述信息
 * ------------------------                     | 
 * |    entry1  (1 block) | contents (n blocks) | rk3399-nanopi4-rev01.dtb文件描述信息
 * ------------------------                     | 
 * |    ......            |                     |
 * ------------------------                     |
 * |    entryn  (1 block) |                     |
 * ----------------------------------------------
 * |    file0  (x blocks)                       | rk3399-nanopi4-rev00.dtb文件内容
 * ----------------------------------------------
 * |    file1  (y blocks)                       | rk3399-nanopi4-rev01.dtb文件内容
 * ----------------------------------------------
 * |                   ......                   |
 * |---------------------------------------------
 * |    filen  (z blocks)                       |
 * ----------------------------------------------

其中header中描述了resource,img的文件信息;

/**
 * struct resource_img_hdr
 *
 * @magic: should be "RSCE"
 * @version: resource image version, current is 0
 * @c_version: content version, current is 0
 * @blks: the size of the header ( 1 block = 512 bytes)
 * @c_offset: contents offset(by block) in the image
 * @e_blks: the size(by block) of the entry in the contents
 * @e_num: numbers of the entrys.
 */
struct resource_img_hdr {      // 一共占用16个字节
        char            magic[4];    // 4个字节  偏移0x00 "RSCE"    
        uint16_t        version;     // 2个字节  偏移0x04  0x00
        uint16_t        c_version;   // 2个字节  偏移0x06  0x00
        uint8_t         blks;        // 1个字节  偏移0x08  0x00
        uint8_t         c_offset;    // 1个字节  偏移0x09  0x01   
        uint8_t         e_blks;      // 1个字节  偏移0x0A  0x01 
        uint32_t        e_nums;      // 4个字节  偏移0x0C  0x17 23个文件
};

struct resource_entry {        // 每一个entry描述一个文件的信息
        char            tag[4];        				// 4个字节    偏移0x00 "ENTR" 
        char            name[MAX_FILE_NAME_LEN];    // 220个字节  偏移0x04
        char            hash[MAX_HASH_LEN];         // 32个字节   偏移0xE0  
        uint32_t        hash_size;                  // 4个字节    偏移0x100 
        uint32_t        blk_offset;                 // 4个字节    偏移0x104  真实文件的偏移(单位为块)
        uint32_t        size;                       // 4个字节    偏移0x108  真实文件大小,单位字节 */
};

(0x04d3)*512    2213 60ef
    
d304  1fa7 0100

其中:

  • c_offset:表示contentsresource.img中的偏移扇区;
  • e_blks:表示contents中每个entry的大小,单位为扇区;
  • e_nums:表示contentsentry的数量;

首先我们获取头部信息,从中可以解析到e_blks=1,e_nums=0x17

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588/debian-bullseye-desktop-arm64$ xxd -b -e -l 16 resource.img
00000000: 45435352 00000000 00010101 00000017  RSCE............

获取第一个entry信息,位于第二个扇区;

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588/debian-bullseye-desktop-arm64$ xxd -b -e -s 512 -l 512 resource.img
00000200: 52544e45 33336b72 6e2d3939 706f6e61  ENTRrk3399-nanop
00000210: 722d3469 30307665 6274642e 00000000  i4-rev00.dtb....
00000220: 00000000 00000000 00000000 00000000  ................
00000230: 00000000 00000000 00000000 00000000  ................
00000240: 00000000 00000000 00000000 00000000  ................
00000250: 00000000 00000000 00000000 00000000  ................
00000260: 00000000 00000000 00000000 00000000  ................
00000270: 00000000 00000000 00000000 00000000  ................
00000280: 00000000 00000000 00000000 00000000  ................
00000290: 00000000 00000000 00000000 00000000  ................
000002a0: 00000000 00000000 00000000 00000000  ................
000002b0: 00000000 00000000 00000000 00000000  ................
000002c0: 00000000 00000000 00000000 00000000  ................
000002d0: 00000000 00000000 00000000 00000000  ................
000002e0: 4fcd8a02 dd3269a8 09debdd0 6ead5934  ...O.i2.....4Y.n
000002f0: acd6427d 00000000 00000000 00000000  }B..............
00000300: 00000014 00000019 0001c2a6 00000000  ................
00000310: 00000000 00000000 00000000 00000000  ................
00000320: 00000000 00000000 00000000 00000000  ................
00000330: 00000000 00000000 00000000 00000000  ................
00000340: 00000000 00000000 00000000 00000000  ................
00000350: 00000000 00000000 00000000 00000000  ................
00000360: 00000000 00000000 00000000 00000000  ................
00000370: 00000000 00000000 00000000 00000000  ................
00000380: 00000000 00000000 00000000 00000000  ................
00000390: 00000000 00000000 00000000 00000000  ................
000003a0: 00000000 00000000 00000000 00000000  ................
000003b0: 00000000 00000000 00000000 00000000  ................
000003c0: 00000000 00000000 00000000 00000000  ................
000003d0: 00000000 00000000 00000000 00000000  ................
000003e0: 00000000 00000000 00000000 00000000  ................
000003f0: 00000000 00000000 00000000 00000000  ................

同样我们可以获取到剩下每一个文件的信息。

2.3.2 dtb_scan

dtb_scan函数支持从distro_dtbresource.imgFIT uImage中加载dtb到内存,其中:

  • distro_dtb:由宏CONFIG_ROCKCHIP_EARLY_DISTRO_DTB控制,由于该宏默认未配置,因此我们暂且忽略这种方式;
  • resource.img:由宏CONFIG_ROCKCHIP_RESOURCE_IMAGE控制,优先级高于CONFIG_ROCKCHIP_RESOURCE_IMAGE
  • FIT uImage:由宏CONFIG_ROCKCHIP_FIT_IMAGE控制,只有在未开启CONFIG_ROCKCHIP_RESOURCE_IMAGE时生效。

由于NanoPC-T6开发板使用了resource.img,因此源码我们仅仅研究LOCATE_RESOURCE分支即可;

static int dtb_scan(void *fdt, int where)
{
        if (where == LOCATE_DISTRO) {   // 未定义
#ifdef CONFIG_ROCKCHIP_EARLY_DISTRO_DTB
                return rockchip_read_distro_dtb(fdt);
#endif
        } else if (where == LOCATE_RESOURCE) {  // 从resource.img找查找设备树
#ifdef CONFIG_ROCKCHIP_RESOURCE_IMAGE   // 定义在include/generated/autoconf.h
                int hash_size = 0;
                char *hash;
                u32 ret;
				// 直接在resource.img中查找rk3588-nanopi6-rev01.dtb
            	// 将rk3588-nanopi6-rev01.dtb读取到fdt_addr
                ret = rockchip_read_resource_dtb(fdt, &hash, &hash_size);
                if (ret) {
                        printf("Failed to load DTB, ret=%d\n", ret);
                        return ret;
                }

            	// 验证dtb的合法性
                if (fdt_check_header(fdt)) {
                        printf("Invalid DTB magic !\n");
                        return -EBADF;
                }
#ifdef CONFIG_ROCKCHIP_DTB_VERIFY
                if (hash_size && fdt_check_hash(fdt,
                        fdt_totalsize(fdt), hash, hash_size)) {
                        printf("Invalid DTB hash !\n");
                        return -EBADF;
                }
#endif
                return 0;
#endif
        } else if (where == LOCATE_FIT) {
#if defined(CONFIG_ROCKCHIP_FIT_IMAGE) && !defined(CONFIG_ROCKCHIP_RESOURCE_IMAGE) // 配置了从FIT uImage,并且不从resource.img加载设备树
                return fit_image_read_dtb(fdt);
#endif
        }

        return -EINVAL;
}
2.3.2.1 rockchip_read_resource_dtb

rockchip_read_resource_dtb函数定义在uboot-rockchip/arch/arm/mach-rockchip/resource_img.c;用于从resource.img找查找设备树rk3588-nanopi6-rev01.dtb,并将其加载到内存fdt_addr

static struct resource_file *resource_get_file(const char *name)
{
        struct resource_file *f;
        struct list_head *node;
    	
    	// 用于从某个分区(可能是boot/recovery/resource中任意一种)获取资源文件,并将文件信息添加到链表entry_head
        if (resource_scan())
                return NULL;

    	// 遍历这些资源文件,查找是否有名字为name的
        list_for_each(node, &entry_head) {
                f = list_entry(node, struct resource_file, link);
            	// 找到匹配项
                if (!strcmp(f->name, name))
                        return f;
        }

        return NULL;
}


int rockchip_read_resource_dtb(void *fdt_addr, char **hash, int *hash_size)
{
        struct resource_file *f = NULL;
        char *dtb_name;
        int ret;

    	// 获取环境变量dtb_name,被配置为rk3588-nanopi6-rev01.dtb
        dtb_name = env_get("dtb_name");
    	// 如果指定了才会进入
        if (dtb_name) {
            	// 在resource.img中查找到rk3588-nanopi6-rev01.dtb
                f = resource_get_file(dtb_name);
                if (!f) {
                        printf("Failed to get %s\n", dtb_name);                 
                        f = resource_get_file(DEFAULT_DTB_FILE);
                }
                if (!f)
                        return -ENODEV;
        }

#ifdef CONFIG_ROCKCHIP_HWID_DTB  // 未配置,不会进入
        if (resource_scan())
                return -ENOENT;

        if (!f)
                f = resource_read_hwid_dtb();
#endif
        /* If no dtb match hardware id(GPIO/ADC), use the default */
        if (!f) 
                f = resource_get_file(DEFAULT_DTB_FILE);

        if (!f)
                return -ENODEV;
		
    	// 将rk3588-nanopi6-rev01.dtb文件读取到fdt_addr
        ret = rockchip_read_resource_file(fdt_addr, f->name, 0, 0);
        if (ret < 0)
                return ret;

    	// 验证该文件不是设备树
        if (fdt_check_header(fdt_addr))
                return -EBADF;

        *hash = f->hash;
        *hash_size = f->hash_size;
        printf("DTB: %s (%d)\n", f->name, f->size);

        return 0;
}
2.3.3 android_fdt_overlay_apply

由于配置了宏CONFIG_ANDROID_BOOT_IMAGECONFIG_OF_LIBFDT_OVERLAY,所以会执行android_fdt_overlay_apply,函数位于common/android_bootloader.c,参数为设备树rk3588-nanopi6-rev01.dtb加载到内存的地址。

该函数的作用是将设备树覆盖(dtbo.img)应用到现有的设备树(fdt)。首先:

  • 根据引导模式和配置,选择正确的引导分区(part_boot)和设备树覆盖分区(part_dtbo);
  • 然后获取引导设备的描述符并根据分区信息填充头信息;
  • 接下来,它检查是否需要应用Ddtbo,分配内存以备份和增加fdt大小,然后应用dtbo覆盖;
  • 成功应用后,它更新环境变量,否则恢复备份并报告错误;
  • 最后,释放分配的内存和头信息。

函数源码如下:

int android_fdt_overlay_apply(void *fdt_addr)
{
        struct andr_img_hdr *hdr;
        struct blk_desc *dev_desc;
        const char *part_boot = PART_BOOT;  // boot,即boot.img镜像所在分区
        disk_partition_t part_info;
        char *fdt_backup;
        char *part_dtbo = PART_DTBO;  // dtbo分区,即dtbo.img镜像所在分区
        char buf[32] = {0};
        ulong fdt_dtbo = -1;
        u32 totalsize;
        int index = -1;
        int ret;

    	 // 启动进入recovery系统?这里我们以normal系统为例
        if (rockchip_get_boot_mode() == BOOT_MODE_RECOVERY) {
#ifdef CONFIG_ANDROID_AB      // 未定义
                bool can_find_recovery;

                can_find_recovery = ab_can_find_recovery_part();
                part_boot = can_find_recovery ? PART_RECOVERY : PART_BOOT;
                part_dtbo = can_find_recovery ? PART_RECOVERY : PART_DTBO;
#else
                part_boot = PART_RECOVERY;    // 引动分区设置为recovery分区
                part_dtbo = PART_RECOVERY;    // dtbo分区设置为recovery分区
#endif
        }
		// 获取启动设备
        dev_desc = rockchip_get_bootdev();
        if (!dev_desc)
                return -ENODEV;

    	// 根据分区名获取分区信息 part_boot="boot"  
        ret = part_get_info_by_name(dev_desc, part_boot, &part_info);
        if (ret < 0)
                return ret;

    	// 获取安卓镜像头信息
        hdr = populate_andr_img_hdr(dev_desc, &part_info);
        if (hdr) {
#ifdef DEBUG
                android_print_contents(hdr);
#endif

                /*
                 * Google requires a/b system mandory from Android Header v3 for
                 * google authentication, that means there is not recovery.
                 *
                 * But for the products that don't care about google authentication,
                 * it's not mandory to use a/b system. So that we use the solution:
                 * boot.img(v3+) with recovery(v2).
                 *
                 * [recovery_dtbo fields]
                 *      recovery.img with boot_img_hdr_v1,2:  supported
                 *      recovery.img with boot_img_hdr_v0,3+: illegal
                 */
                if ((hdr->header_version == 0) ||
                    (hdr->header_version >= 3 && !strcmp(part_boot, PART_RECOVERY)))
                        goto out;
        }

#ifdef CONFIG_VENDOR_FRIENDLYELEC
    	// 根据分区名获取分区信息 PART_BOOT="dtbo"  
        if (part_get_info_by_name(dev_desc, PART_DTBO, &part_info) > 0) {
                part_dtbo = PART_DTBO;
        } else {
                printf("No %s partition\n", PART_DTBO);
        }
#endif

    	// 将dtbo分区加载到内存, 选择合适的设备树索引(存放在index)、从DT映像中提取指定索引的设备树(地址存放在fdt_dtbo)
        ret = android_get_dtbo(&fdt_dtbo, (void *)hdr, &index, part_dtbo);
        if (!ret) {  // 进入
                phys_size_t fdt_size;

                /* Must incease size before overlay */
                fdt_size = fdt_totalsize((void *)fdt_addr) +
                                fdt_totalsize((void *)fdt_dtbo);
                if (sysmem_free((phys_addr_t)fdt_addr))
                        goto out;

                if (!sysmem_alloc_base(MEM_FDT_DTBO,
                                       (phys_addr_t)fdt_addr,
                                        fdt_size + CONFIG_SYS_FDT_PAD))
                        goto out;
                /*
                 * Backup main fdt in case of being destroyed by
                 * fdt_overlay_apply() when it overlys failed.
                 */
                totalsize = fdt_totalsize(fdt_addr);
                fdt_backup = malloc(totalsize);
                if (!fdt_backup)
                        goto out;

                memcpy(fdt_backup, fdt_addr, totalsize);
                fdt_increase_size(fdt_addr, fdt_totalsize((void *)fdt_dtbo));
            	// 把dtbo文件的内容,插入fdt_dtbo设备树中去
                ret = fdt_overlay_apply(fdt_addr, (void *)fdt_dtbo);
                if (!ret) {
                    	// 更新bootargs环境变量,追加androidboot.dtbo_idx=1
                        snprintf(buf, 32, "%s%d", "androidboot.dtbo_idx=", index);
                        env_update("bootargs", buf);
                    	// 在uboot输出日志中可以定位到该行
                        printf("ANDROID: fdt overlay OK\n");
                } else {
                    	// 设备则还原设备树
                        memcpy(fdt_addr, fdt_backup, totalsize);
                        printf("ANDROID: fdt overlay failed, ret=%d\n", ret);
                }

                free(fdt_backup);
        }
out:
        if (hdr)
                free(hdr);

        return 0;
}

实际上dtbo.img镜像中存放了/chosen节点bootargs_ext 属性信息;

root=/dev/mmcblk0p8 rw rootfstype=ext4 data=/dev/mmcblk0p9 consoleblank=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1

可以使用HEX-Editor编辑器打开该文件自行查看。

2.4 resource_scan

resource_scan定义在uboot-rockchip/arch/arm/mach-rockchip/resource_img.c,用于从某个分区(可能是boot/recovery/resource中任意一种)获取资源文件,并将文件信息添加到链表entry_head

static int resource_scan(void)
{
		// 获取启动设备,比如SD卡启动,获取到的启动设备为mmc1
        struct blk_desc *desc = rockchip_get_bootdev();
        __maybe_unused int ret;

        if (!desc) {
                printf("RESC: No bootdev\n");
                return -ENODEV;
        }

    	// 实际上此时这里并不会为空,因为在board_init执行时就会调用一次init_kernel_dtb -> rockchip_read_dtb_file,已经初始化了
        if (!list_empty(&entry_head))
                return 0;

#ifdef CONFIG_ROCKCHIP_FIT_IMAGE          // 配置了
    	// 由于烧录的boot.img并不是FIT uImage镜像,因此该函数执行返回EAGAIN
        ret = fit_image_init_resource(desc);
        if (!ret || ret != -EAGAIN)
                return ret;
#endif
#ifdef CONFIG_ROCKCHIP_UIMAGE             // 未定义
        ret = uimage_init_resource(desc);
        if (!ret || ret != -EAGAIN)
                return ret;
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE          // 配置了   
        disk_partition_t part;
        ulong blk_offset;
        char hdr[512];
        char name[32];

        /* partition priority: boot/recovery > resource */
        if (!android_image_init_resource(desc, &part, &blk_offset)) { // 不会进入
                if (blk_dread(desc, part.start + blk_offset, 1, hdr) != 1)
                        return -EIO;

                if (resource_check_header((void *)hdr)) {
                        strcpy(name, (char *)part.name);
                        if (resource_default(desc, &part, &blk_offset))
                                return -ENOENT;

                        printf("RESC: '%s' -> '%s'\n", name, part.name);
                }
        } else {
            	// 获取resource分区信息(resource.img)
                if (resource_default(desc, &part, &blk_offset))
                        return -ENOENT;
        }

        /* now, 'part' can be boot/recovery/resource */
    	// 用于从part分区获取所有文件信息,并添加到链表entry_head
        return resource_init(desc, &part, blk_offset);
#endif
        return -ENOENT;
}
2.4.1 fit_image_init_resource

函数fit_image_init_resource定义在uboot-rockchip/arch/arm/mach-rockchip/fit.c,由于烧录的boot分区烧录的boot.img并不是FIT uImage镜像,因此该函数执行返回EAGAIN

判断boot.img是不是FIT uImage格式很简单,通过file命令可以查看文件信息是否有Device Tree Blob关键字;

root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588/debian-bullseye-desktop-arm64$ file boot.img
boot.img: data
root@ubuntu:/work/sambashare/rk3588/friendly/sd-fuse_rk3588/debian-bullseye-desktop-arm64$ file /work/sambashare/rk3399/rkdeveloptool/kernel.itb
/work/sambashare/rk3399/rkdeveloptool/kernel.itb: Device Tree Blob version 17, size=15519328, boot CPU=0, string block size=108, DT structure block size=15518268

fit_image_init_resource源码如下;

#ifdef CONFIG_ROCKCHIP_RESOURCE_IMAGE
ulong fit_image_init_resource(struct blk_desc *dev_desc)
{
        disk_partition_t part;
        void *fit, *buf;
        int offset, size;
        int ret = 0;

        if (!dev_desc)
                return -ENODEV;
		// 获取boot分区第一个扇区数据,地址范围0x00000000~0x00000027表示的是fdt_header 结构体的成员信息,并对该结构体进行校验判断是不是FIT uImage
    	// 如果是FIT uImage,得到image data file在boot.img中的偏移地址,即kernel、ramdisk、dtb等文件存放地址
    	// 由于我们boot分区烧录的不是FIT uImage,而是Android bootimg,因此这里会执行失败
        fit = fit_get_blob(dev_desc, &part, true);
        if (!fit)
                return -EAGAIN;

    	// 获取multi节点"data-size"属性以及"data-position"属性属性值
        ret = fdt_image_get_offset_size(fit, FIT_MULTI_PROP, &offset, &size);
        if (ret)
                return -EINVAL;

        buf = memalign(ARCH_DMA_MINALIGN, ALIGN(size, dev_desc->blksz));
        if (!buf)
                return -ENOMEM;

        printf("RESC: '%s', blk@0x%08lx\n", part.name,
               part.start + ((FIT_ALIGN(fdt_totalsize(fit)) + offset) / dev_desc->blksz));
        ret = fit_image_load_one(fit, dev_desc, &part, FIT_MULTI_PROP, buf, 1);
        if (ret)
                return ret;

        ret = resource_setup_ram_list(dev_desc, buf);
        if (ret) {
                FIT_I("Failed to setup resource ram list, ret=%d\n", ret);
                free(fit);
                return ret;
        }

        fit_msg(fit);
        free(fit);

  }
  else
  ......
  #endif
2.4.1.1 fit_get_blob

fit_get_blob解析FIT uImage镜像,将image data file内容加载到内存,并返回该加载地址。

如果进入的是normal系统,则从boot分区获取FIT uImage镜像;如果进入的是recovery系统,则从recovery分区获取FIT uImage镜像。

static void *fit_get_blob(struct blk_desc *dev_desc,
                          disk_partition_t *out_part,
                          bool verify)
{
        __maybe_unused int conf_noffset;
        disk_partition_t part;
        char *part_name = PART_BOOT;
        void *fit, *fdt;
        int blk_num;

#ifndef CONFIG_ANDROID_AB               // 未定义
        if (rockchip_get_boot_mode() == BOOT_MODE_RECOVERY)
                part_name = PART_RECOVERY;
#endif
		// 根据分区名获取分区信息part_name=PART_BOOT即"boot"  
        if (part_get_info_by_name(dev_desc, part_name, &part) < 0) {
                FIT_I("No %s partition\n", part_name);
                return NULL;
        }

        *out_part = part;
    	// 除法运算,向上取整 应该为 1 
        blk_num = DIV_ROUND_UP(sizeof(struct fdt_header), dev_desc->blksz);
        // 1*512=512
        fdt = memalign(ARCH_DMA_MINALIGN, blk_num * dev_desc->blksz);
        if (!fdt)
                return NULL;
		// 读取第一个扇区数据到fdt,地址范围0x00000000~0x00000027表示的是fdt_header结构体的成员信息
        if (blk_dread(dev_desc, part.start, blk_num, fdt) != blk_num) {
                debug("Failed to read fdt header\n");
                goto fail;
        }

    	// 对该结构体进行校验判断是不是FIT uImage
        if (fdt_check_header(fdt)) {
                debug("No fdt header\n");
                goto fail;
        }

        if (!fit_is_ext_type(fdt)) {
                debug("Not external type\n");
                goto fail;
        }

    	// 计算FIT区域占用几个扇区
        blk_num = DIV_ROUND_UP(fdt_totalsize(fdt), dev_desc->blksz);
       // 得到image data file在boot.itb中的偏移地址,即kernel、ramdisk、dtb等文件存放地址
        fit = memalign(ARCH_DMA_MINALIGN, blk_num * dev_desc->blksz);
        if (!fit) {
                debug("No memory\n");
                goto fail;
        }

    	// 将FIT uImage所在分区内容加载到内存地址fit
        if (blk_dread(dev_desc, part.start, blk_num, fit) != blk_num) {
                free(fit);
                debug("Failed to read fit blob\n");
                goto fail;
        }

#ifdef CONFIG_FIT_SIGNATURE         // 未定义
		......
#endif
        return fit;

fail:
        free(fdt);
        return NULL;
}
2.4.2 android_image_init_resource

android_image_init_resource定义在uboot-rockchip/common/image-android.c

#ifdef CONFIG_ROCKCHIP_RESOURCE_IMAGE   // 配置了
int android_image_init_resource(struct blk_desc *desc,
                                disk_partition_t *out_part,
                                ulong *out_blk_offset)
{
        struct andr_img_hdr *hdr = NULL;
        const char *part_name = ANDROID_PARTITION_BOOT;   // boot
        disk_partition_t part;
        ulong offset;
        int ret = 0;

        if (!desc)
                return -ENODEV;

#ifndef CONFIG_ANDROID_AB
        if (rockchip_get_boot_mode() == BOOT_MODE_RECOVERY)
                part_name = ANDROID_PARTITION_RECOVERY;
#endif
		// 根据分区名获取分区信息part_name=ANDROID_PARTITION_BOOT即"boot"  
        if (part_get_info_by_name(desc, part_name, &part) < 0)
                return -ENOENT;

        hdr = populate_andr_img_hdr(desc, &part);
        if (!hdr)
                return -EINVAL;

        if (hdr->header_version >= 2 && hdr->dtb_size)
                env_update("bootargs", "androidboot.dtb_idx=0");

        if (hdr->header_version <= 2) {
                offset = hdr->page_size +
                        ALIGN(hdr->kernel_size, hdr->page_size) +
                        ALIGN(hdr->ramdisk_size, hdr->page_size);
                *out_part = part;
                *out_blk_offset = DIV_ROUND_UP(offset, desc->blksz);
        } else {
                ret = -EINVAL;
        }
        free(hdr);

        return ret;
}
#endif
2.4.3 resource_default

resource_default定义在uboot-rockchip/arch/arm/mach-rockchip/resource_img.c,用于获取resource分区信息;

static int resource_default(struct blk_desc *desc,
                            disk_partition_t *out_part,
                            ulong *out_blk_offset)
{
        disk_partition_t part;

		// 根据分区名获取分区信息 PART_RESOURCE=resource
        if (part_get_info_by_name(desc, PART_RESOURCE, &part) < 0)
                return -ENODEV;

        *out_part = part;
        *out_blk_offset = 0;

        return 0;
}

2.4.4 resource_init

resource_init定义在uboot-rockchip/arch/arm/mach-rockchip/resource_img.c, 用于从指定分区(比如resource)获取所有文件信息,并添加到链表entry_head

static int resource_check_header(struct resource_img_hdr *hdr)
{
    	// 判断magic是不是“RSCE”
        return memcmp(RESOURCE_MAGIC, hdr->magic, RESOURCE_MAGIC_SIZE);
}

static int resource_init(struct blk_desc *desc,
                         disk_partition_t *part,
                         ulong blk_offset)
{
        printf("RESC: '%s', blk@0x%08lx\n", part->name, part->start + blk_offset);

#ifdef CONFIG_ANDROID_AVB  // 配置了
        char hdr[512];
        ulong resc_buf = 0;
        int ret;

    	// 读取1个分区的数据到hdr
        if (blk_dread(desc, part->start, 1, hdr) != 1)
                return -EIO;

        /* only handle android boot/recovery.img and resource.img, ignore fit */
    	// 根据头部信息判断是不是android镜像,或者是不是resource.img
        if (!android_image_check_header((void *)hdr) ||
            !resource_check_header((void *)hdr)) {
                ret = android_image_verify_resource((const char *)part->name, &resc_buf);
                if (ret) {
                        printf("RESC: '%s', avb verify fail: %d\n", part->name, ret);
                        return ret;
                }

                /*
                 * unlock=0: resc_buf is valid and file was already full load in ram.
                 * unlock=1: resc_buf is 0.
                 */
                if (resc_buf && !resource_check_header((void *)resc_buf))
                        return resource_setup_ram_list(desc, (void *)resc_buf);
        }
#endif

        return resource_setup_blk_list(desc, part->start + blk_offset);
}
2.4.4.1 resource_setup_blk_list
static int resource_setup_blk_list(struct blk_desc *desc, ulong blk_start)
{
        struct resource_img_hdr *hdr;
        int blk_cnt;
        int ret = 0;
        void *buf;

        hdr = memalign(ARCH_DMA_MINALIGN, desc->blksz);
        if (!hdr)
                return -ENOMEM;

        if (blk_dread(desc, blk_start, 1, hdr) != 1) {
                ret = -EIO;
                goto out;
        }

    	// 校验resource header
        if (resource_check_header(hdr)) {
                printf("RESC: invalid\n");
                if (fdt_check_header(hdr)) {
                        ret = -EINVAL;
                        goto out;
                } else {
                        /* this is a dtb file */
                        printf("RESC: this is dtb\n");
                        ret = resource_add_file(DEFAULT_DTB_FILE,
                                                fdt_totalsize(hdr),
                                                blk_start, 0, NULL, 0, false);
                        goto out;
                }
        }

        blk_cnt = hdr->e_blks * hdr->e_nums;
        hdr = realloc(hdr, (1 + blk_cnt) * desc->blksz);
        if (!hdr) {
                ret = -ENOMEM;
                goto out;
        }

        buf = (void *)hdr + desc->blksz;
        if (blk_dread(desc, blk_start + hdr->c_offset, blk_cnt, buf) != blk_cnt) {
                ret = -EIO;
                goto out;
        }

        resource_setup_list(desc, blk_start, hdr, false);
        resource_setup_logo_bmp(desc);
out:
        free(hdr);

        return ret;
}
2.4.4.2 resource_setup_list
// 遍历所有的资源文件,并添加到链表entry_head
static int resource_setup_list(struct blk_desc *desc, ulong blk_start,
                               void *resc_hdr, bool in_ram)
{
        struct resource_img_hdr *hdr = resc_hdr;
        struct resource_entry *et;
        u32 i, stride;
        void *pos;

        pos = (void *)hdr + hdr->c_offset * desc->blksz;
        stride = hdr->e_blks * desc->blksz;

        for (i = 0; i < hdr->e_nums; i++) {
                et = pos + (i * stride);
                if (memcmp(et->tag, ENTRY_TAG, ENTRY_TAG_SIZE))
                        continue;

                resource_add_file(et->name, et->size,
                                  blk_start, et->blk_offset,
                                  et->hash, et->hash_size, in_ram);
        }

        return 0;
}

三、distro_bootcmd

uboot支持distro_bootcmd启动方式;

=> print distro_bootcmd
distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done

如上,distro_bootcmd遍历执行boot_targets,如下:

=> pri boot_targets
boot_targets=mmc1 mmc0 mtd2 mtd1 mtd0 usb0 pxe dhcp

可见,最终是执行bootcmd_{mmc1mmc0mtd2mtd1mtd0usb0pxedhcp}、中的一个。

3.1 mmc list

对于NanoPC-T6开发板,只有mmc0mmc1有效,在命令行输入mmc list命令用于来查看当前开发板一共有几个MMC设备:

=> mmc list
mmc@fe2c0000: 1 (SD)         # 代表SD卡
mmc@fe2e0000: 0              # 代表eMMC

=> pri bootcmd_mmc1
bootcmd_mmc1=setenv devnum 1; run mmc_boot

=> pri bootcmd_mmc0
bootcmd_mmc0=setenv devnum 0; run mmc_boot

默认会将eMMC设置为当前设备,如果需要查看eMMC信息,运行如下命令:

Device: mmc@fe2e0000
Manufacturer ID: d6
OEM: 103
Name: A3A44
Timing Interface: HS400 Enhanced Strobe
Tran Speed: 200000000
Rd Block Len: 512
MMC version 5.1
High Capacity: Yes
Capacity: 230.5 GiB
Bus Width: 8-bit DDR
Erase Group Size: 512 KiB
HC WP Group Size: 8 MiB
User Capacity: 230.5 GiB WRREL
Boot Capacity: 4 MiB ENH
RPMB Capacity: 4 MiB ENH

从上图中可以看到MMC设备版本为5.1, 230.5 GiB,速度为200000000Hz=200MHz, 8位宽的总线。

3.2 mmc_boot

无论bootcmd_mmc0还是bootcmd_mmc1来启动内核,都是调用mmc_boot,如下:

=> pri mmc_boot
mmc_boot=if mmc dev ${devnum}; then setenv devtype mmc; run scan_dev_for_boot_part; fi

=> pri scan_dev_for_boot_part
scan_dev_for_boot_part=part list ${devtype} ${devnum} -bootable devplist; env exists devplist || setenv devplist 1; for distro_bootpart in ${devplist}; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; done

可见,第一步是探测mmc是否存在if mmc dev ${devnum},第二步则执行scan_dev_for_boot_part命令来获取分区信息,如下:

=> part list mmc 1

Partition Map for MMC device 1  --   Partition Type: EFI

Part    Start LBA       End LBA         Name
        Attributes
        Type GUID
        Partition GUID
  1     0x00004000      0x00005fff      "uboot"
        attrs:  0x0000000000000000
        type:   f808d051-1602-4dcd-9452-f9637fefc49a
        guid:   b750e44e-833f-4a30-c38c-b117241d84d4
  2     0x00006000      0x00007fff      "misc"
        attrs:  0x0000000000000000
        type:   c6d08308-e418-4124-8890-f8411e3d8d87
        guid:   a1c81622-7741-47ad-b846-c6972488d396
  3     0x00008000      0x00009fff      "dtbo"
        attrs:  0x0000000000000000
        type:   2a583e58-486a-4bd4-ace4-8d5454e97f5c
        guid:   43784a32-a03d-4ade-92c6-ede64ff9b794
  4     0x0000a000      0x00011fff      "resource"
        attrs:  0x0000000000000000
        type:   6115f139-4f47-4baf-8d23-b6957eaee4b3
        guid:   000b305f-484a-4582-9090-4ad0099d47bd
  5     0x00012000      0x00025fff      "kernel"
        attrs:  0x0000000000000000
        type:   a83fba16-d354-45c5-8b44-3ec50832d363
        guid:   24eeb649-277f-4c11-ffeb-d9f20027a83b
  6     0x00026000      0x00035fff      "boot"
        attrs:  0x0000000000000000
        type:   500e2214-b72d-4cc3-d7c1-8419260130f5
        guid:   1cac805f-726a-495a-fd35-821355a6e7e8
  7     0x00036000      0x00045fff      "recovery"
        attrs:  0x0000000000000000
        type:   e099da71-5450-44ea-aa9f-1b771c582805
        guid:   2bfee623-d83c-426a-ab80-21732c9bb7d3
  8     0x00046000      0x00805fff      "rootfs"
        attrs:  0x0000000000000000
        type:   af12d156-5d5b-4ee3-b415-8d492ca12ea9
        guid:   b2af085d-a675-48c6-c437-f6d557ff4744
  9     0x00806000      0x00e87524      "userdata"
        attrs:  0x0000000000000000
        type:   8eb9ee49-e963-4bb6-fd75-f30618df9dcd
        guid:   2d9e7b61-1b31-47e7-ee0d-8cec26d42ef6

如上输出了SD设备的分区信息,可以看到是和parameter一致的,这是因为我们烧录到SD卡的镜像文件rk3588-sd-debian-bullseye-desktop-6.1-arm64-20240714.img就是这么分区的。

对于可引导分区是有标识的,因此首先获取哪个分区可以引导,并将结果放在devplist变量中。然后执行if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype;来确认是否是可引导分区,如下:

=>  fstype mmc 1:5 bootfstype
Failed to mount ext2 filesystem...
** Unrecognized filesystem type **

最后使用scan_dev_for_boot来查找启动目录和文件,如下:

=> pri scan_dev_for_boot
scan_dev_for_boot=echo Scanning ${devtype} ${devnum}:${distro_bootpart}...; for prefix in ${boot_prefixes}; do run scan_dev_for_extlinux; run scan_dev_for_scripts; done;

首先认为启动文件在/目录或者/boot/,其它目录不考虑,如下:

=> pri boot_prefixes
boot_prefixes=/ /boot/

然后,使用scan_dev_for_extlinux/scan_dev_for_scripts检测启动方式,这两种启动方式对应的内核镜像格式是完全不同的。

3.2.1 scan_dev_for_extlinux
=>  pri scan_dev_for_extlinux
scan_dev_for_extlinux=if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}extlinux/extlinux.conf; then echo Found ${prefix}extlinux/extlinux.conf; run boot_extlinux; echo SCRIPT FAILED: continuing...; fi

查找/extlinux/extlinux.conf/boot/extlinux/extlinux.conf文件,如果存在,则执行sysboot命令,如下;

=> pri boot_extlinux
boot_extlinux=sysboot ${devtype} ${devnum}:${distro_bootpart} any ${scriptaddr} ${prefix}extlinux/extlinux.conf

对于这一种内核引导方式,我们需要制作ext2文件系统镜像文件,具体可以参考《distro boot启动内核镜像制作》。

3.2.2 scan_dev_for_scripts
=> pri scan_dev_for_scripts
scan_dev_for_scripts=for script in ${boot_scripts}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${script}; then echo Found U-Boot script ${prefix}${script}; run boot_a_script; echo SCRIPT FAILED: continuing...; fi; done

遍历boot_scripts

=> pri boot_scripts
boot_scripts=boot.scr.uimg boot.scr

判断/或者/boot目录是否存在boot_scripts中指定的文件,如果存在使用boot_a_script将脚本加载到内存并执行,如下:

=> pr boot_a_script
boot_a_script=load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script}; source ${scriptaddr}

对于这一种内核引导方式,我们需要制作FAT32文件系统镜像文件,具体可以参考《Rockchip RK3566 - orangepi-build编译boot相关内容。

参考文章

[1] Mini2440uboot移植之源码分析start.S(一)]

[2] Mini2440之uboot移植之裁剪、分区与环境变量设置(五)

[3] Rockchip RK3399 - 移植linux 5.2.8

[4] Mini2440uboot移植之源码分析命令解析(五)

[5] 基于rk3566uboot分析 - dts加载和dm模型的本质

[6] Rockchip RK3588 - 移植uboot 2017.09 & linux 6.1(友善之家脚本方式)

[7] 树莓派4B(rpi4b)引导ubuntu分析

[8] 从设备树(dtb格式数据)中解析出bootargs

posted @ 2024-07-14 00:14  大奥特曼打小怪兽  阅读(1827)  评论(1编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步