bootm调用流程
本文作为一个笔记,记录bootm启动的流程,参考版本:uboot 2021.01
在执行bootm之前,image已经从sd卡读到了内存中,这个要看uboot的参数,是从哪里读取读到哪里
下面的是qemu的下载命令:uboot的命令可以放在代码里面可以放在环境变量里面也可以放在文件里面,例如boot.scr。
fatload mmc 0 0xa1000000 uImage.lz4
fatload mmc 0 0xa8300000 uInitrd.lz4
fatload mmc 0 0xa8000000 kernel.dtb
bootm – 执行命令 : bootm 0xa1000000 0xa8300000 0xa8000000
do_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) – cmd/bootm.c
A. argc–; argv++; //去掉了命令参数中的argv[0],也就是bootm
B. // 如果states包含BOOTM_STATE_OS_GO此函数最终就会boot an OS, 那么这个函数就不会返回了,除非出错了。
// 这里传入的States是0x71f,所以会执行很多子函数
do_bootm_states(cmdtp, flag, argc, argv, states, &images, 1) -- common/bootm.c
1.1 if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv); -- common/bootm.c
memset 全局变量images,image状态设置为start
2.1 if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv); -- common/bootm.c
2.1.1 boot_get_kernel -- 读取image的64 bytes的头
//获取image的内存地址,是在调用bootm之前,由其他程序把image读到内存中,不同的介质不同的读取方法。
// 从bootm的参数里面获取这个image的地址,argv在do_bootm里面做了++
img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0],&fit_uname_config, &fit_uname_kernel);
hdr = image_get_kernel(img_addr, images->verify); 获取image的头信息
image_print_contents(hdr); -- 打印头信息,类似下面的信息:
Image Name: Linux
Image Type: RISC-V Linux Kernel Image (lz4 compressed)
Data Size: 4025467 Bytes = 3.8 MiB
Load Address: a0400000
Entry Point: a0400000
image_check_dcrc(hdr) -- crc校验image
memmove(&images->legacy_hdr_os_copy, hdr, sizeof(image_header_t)); -- 拷贝头信息,避免解压的时间覆盖头信息
2.1.2 根据上面获取到header信息设置images.os中的变量,类似下面的信息:
走到这里kernel的所有信息都从header中获取了,如image类型,压缩类型,load地址,entry地址,size等等
3.1 if (!ret && (states & BOOTM_STATE_FINDOTHER))
通过bootm的参数,试图获取有效的image,并把信息保存在images里面
ret = bootm_find_other(cmdtp, flag, argc, argv); -- common/bootm.c
boot_get_ramdisk -- 获取ramdisk,保存ramdisk的信息,并打印如下:
## Loading init Ramdisk from Legacy Image at a8300000 ...
Image Name: Initrd
Image Type: RISC-V Linux RAMDisk Image (lz4 compressed)
Data Size: 5487619 Bytes = 5.2 MiB
Load Address: 00000000
Entry Point: 00000000
Verifying Checksum ... OK
boot_get_fdt -- 获取dtb,并打印如下:
## Flattened Device Tree blob at a8000000
Booting using the fdt blob at 0xa8000000
4.1 /* Load the OS */
if (!ret && (states & BOOTM_STATE_LOADOS)) {
iflag = bootm_disable_interrupts();
//主要做的就是从内存中0xa1000040读取kernel,跳过了64bytes的header,
//并根据压缩类型进行解压缩,并把解压缩的image放在Load Address中即0xa0400000
ret = bootm_load_os(images, 0); -- common/bootm.c
image_decomp() -- riscv qemu使用的是lz4,所以会使用lz4解压缩
//load_buf=0xa0400000 image_buf=0xa1000040, 直接从0xa1000000读取,跳过uboot header,解压到load到地址
ulz4fn(image_buf, image_len, load_buf, &size);
LZ4_decompress_generic()
到这里uimage经过了去头,解压缩的动作,后面可以直接跳过去执行了
5.1 if (!ret && (states & BOOTM_STATE_RAMDISK)) {
//重定位ramdisk的位置- relocate init ramdisk
ret = boot_ramdisk_high(&images->lmb, images->rd_start,rd_len, &images->initrd_start, &images->initrd_end);
6.1 boot_fn = bootm_os_get_boot_func(images->os.os);
通过os的类型,获取回调函数,linux的是 do_bootm_linux
这个函数会根据arch来实现,每一个arch下面都有一个,riscv调用的在bootm.c arch\riscv\lib
7.1 ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
//调用riscv的do_bootm_linux,根据falge 来执行准备工作,只是准备工作,不是boot
//一般就是做boot前的检查
if (flag & BOOTM_STATE_OS_PREP) {
boot_prep_linux(images);
return 0;
}
8.1 ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn);
//根据参数BOOTM_STATE_OS_GO,进行最后的boot工作,
boot_fn(state, argc, argv, images); ---去引导kernel了,不会返回了,除非是Stand-alone program
boot_jump_linux(images, flag);
kernel = (void (*)(ulong, void *))images->ep; 也就是最前面获取到entry地址0xa0400000
kernel(gd->arch.boot_hart, images->ft_addr); -- 启动kernel,后面就交给了kernel,传入的是machine id和dtb的地址