Rockchip RK3588 - uboot引导方式介绍
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T6
开发板
eMMC
:256GB
LPDDR4
:16GB
显示屏 :15.6
英寸HDMI
接口显示屏
u-boot
:2017.09
linux
:6.1
----------------------------------------------------------------------------------------------------------------------------
在前面的文章我们对Rockhip Linux SDK
进行了深入分析,其中涉及到了SDK
编译过程、编译源码,具体可以参考:
Rockchip RK3588 - Rockchip Linux SDK
编译;Rockchip RK3588 - Rockchip Linux SDK Buildroot
文件系统构建;Rockchip RK3588 - Rockchip Linux SDK
脚本分析。
此外,我们还是深入分析了Recovery
模式下的系统升级功能,具体可参考:
Rockchip RK3588 - Rockchip Linux Recovery updateEngine
源码分析;Rockchip RK3588 - Rockchip Linux Recovery updateEngine
测试。
接下来我们将尝试在NanoPC-T6
开发板实现系统升级功能,当然我们还期望当根文件系统损坏时,开发板能够通过按住GPIO
口进入到recovery
系统恢复正常系统。由于该系列内容较长,我们分为多篇文章介绍:
一、uboot
启动方式
既然要实现在NanoPC-T6
开发板实现系统升级功能,我们就需要了解uboot
启动内核的方式,并制作以下分区镜像;
misc.img
:misc
分区是一个没有文件系统的分区,用于存放一些引导配置参数;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.img
(kernel + dtb + ramdisk
);rootfs
分区:供buildroot
、debian
或yocto
编出来的rootfs.img
;userdata
分区:供APP
临时生成文件或给最终用户使用,挂载在/userdata
目录下。
从上面我们可以看到这里有两个分区时存放了内核镜像,分别是boot
和kernel
,那问题来了,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
:从eMMC
中boot/recovery
分区(如果进入的是normal
系统,则为boot
分区;如果进入的是recovery
系统,则为recovery
分区)加载FIT uImage
镜像文件(通常由kernel
+dtb
+ramdisk
组成)到内存,然后启动内核 ;boot_android
:启动Android
内核镜像;bootrkp
:通常用于Rockchip
平台上的特定启动操作,可能用于启动特定的固件或者特殊的操作模式;distro_bootcmd
:运行uboot
环境中定义的distro_bootcmd
,这是一个uboot
环境变量,通常包含了一系列的启动命令,比如尝试从网络引导、从存储设备引导等;
其中boot_fit
、distro_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
命令。
有关bootrkp
和distro_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_dt
对dtb
里面的所有节点进行扫描,用提供的回调函数循环处理节点信息,回调函数返回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 dtb
中bootargs
来源
实际上dtb
中的bootargs
的来源有如下几种;
- 内核启动参数
bootargs
保存在dts
的chosen
节点的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
;其中rk3399
和rk3588
SDK
的u-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
节点的bootargs
、bootargs_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 Flash
、SD
卡)的初始化配置、默认的启动命令等。这些信息是针对特定硬件的,确保uboot
能够正确地操作和引导该硬件平台。
二、bootrkp
bootrkp
命令的实现位于uboot-rockchip/cmd/bootrkp.c
,uboot
中使用宏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_r
、kernel_addr_r
、fdt_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
:表示contents
在resource.img
中的偏移扇区;e_blks
:表示contents
中每个entry
的大小,单位为扇区;e_nums
:表示contents
中entry
的数量;
首先我们获取头部信息,从中可以解析到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_dtb
、resource.img
、FIT 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_IMAGE
和CONFIG_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
_{mmc1
、mmc0
、mtd2
、mtd1
、mtd0
、usb0
、pxe
、dhcp
}、中的一个。
3.1 mmc list
对于NanoPC-T6
开发板,只有mmc0
、mmc1
有效,在命令行输入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] Mini2440
之uboot
移植之源码分析start.S
(一)]
[2] Mini2440之uboot
移植之裁剪、分区与环境变量设置(五)
[3] Rockchip RK3399
- 移植linux 5.2.8
[4] Mini2440
之uboot
移植之源码分析命令解析(五)
[5] 基于rk3566
的uboot
分析 - dts
加载和dm
模型的本质