----------------------------------------------------------------------------------------------------------------------------
开发板 :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@zhengyang:/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 | 镜像文件 | Start (sector) | Start (sector) End (sector) | Size | Name |
---|---|---|---|---|---|
1 | uboot.img | 0x4000 | 0x5FFF | 4M | uboot |
2 | misc.img | 0x6000 | 0x7FFF | 4M | misc |
3 | dtbo.img | 0x8000 | 0x9FFF | 4M | dtbo |
4 | resource.img | 0xa000 | 0x11FFF | 16MB | resource |
5 | kernel.img | 0x12000 | 0x25FFF | 40MB | kernel |
6 | boot.img | 0x26000 | 0x35FFF | 32MB | boot |
7 | recovery.img | 0x36000 | 0x45FFF | 32MB | recovery |
8 | rootfs.img | 0x46000 | 0x804FFF | 3968GB | rootfs |
9 | userdata.img | 0x806000 | - | userdata |
其中:
uboot
分区:供uboot
编译出来的uboot.img
;misc
分区:引导参数分区,供misc.img
,给recovery
使用;dtbo
::供kernel
编译出来的dtbo.img
;resource
:资源分区,由设备树、图片资源文件组成,不包含内核;boot
:供kernel
编译出来的boot.img
(kernel + dtb
,对于该类型镜像uboot
需要采用distro_boo extlinux
引导方式);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 烧录固件
我们将固件烧录到开发板;
给开发板上电,输出的日志中有如下内容:
- Hit key to stop autoboot('CTRL+C'): 0
- ## Booting FIT Image FIT: No fit blob
- FIT: No FIT image
- ANDROID: reboot reason: "(none)"
- Not AVB images, AVB skip
- No valid android hdr
- Android image load failed
- Android boot failed, error -1.
-
- ## Booting Rockchip Format Image
- fdt @ 0x08300000 (0x000421c2)
- kernel @ 0x00400000 (0x021c7808)
- ramdisk @ 0x0a200000 (0x007b2bc0)
- 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 00000000083451c1
日志中说我们采用的内核镜像既不是是Android
,也不是FIT uImage
。
接着又输出
在uboot
命令行查看启动命令:
1.2 boot
启动命令
当我们在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"
- ......
- };
默认启动命令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/rockchip-common.h
中定义的宏RKIMG_BOOTCOMMAND
,而进行了重定义。
这里支持了4中启动方式:
boot_fit
:从eMMC
中boot
分区加载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.2.1 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 = env_get_ulong("ramdisk_addr_r", 16, 0);
- kernel_addr_r = env_get_ulong("kernel_addr_r", 16, 0);
- fdt_addr_r = env_get_ulong("fdt_addr_r", 16, 0);
-
- 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;
- }
-
- 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;
- }
-
- ramdisk_size = read_rockchip_image(dev_desc, boot_part,
- (void *)ramdisk_addr_r);
- if (ramdisk_size < 0)
- ramdisk_size = 0;
-
- 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];
-
- 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;
- }
二、镜像制作
2.1 misc.img
2.2 recovery.img
本节我们将参考Rockchip Linux SDK
尝试自行制作一个recovery.img
系统镜像,并将其烧录到NanoPC-T6
开发板,用来实现该开发板的OTA
升级功能。
recovery.img
系统镜像由kernel + dtb + ramdisk
三部分组成。
2.2.1 Kernrl
2.2.2 dtb
2.2.3 ramdisk
参考文章
[1] Mini2440
之uboot
移植之源码分析start.S
(一)]
[2] Mini2440之uboot
移植之裁剪、分区与环境变量设置(五)