Bootm启动流程分析

Bootm启动流程分析

    • 如何引导内核
      • uboot启动命令
      • 内核镜像介绍
      • 内核启动前提
    • Bootm命令详解
      • Bootm命令格式
      • do_bootm
        • do_bootm_subcommand
        • images全局变量
      • do_bootmd_states
        • bootm_start
        • bootm_find_os
          • boot_get_kernel
        • bootm_find_other
        • bootm_load_os
        • boot_ramdisk_high
        • boot_relocate_fdt
        • bootm_os_get_boot_func
        • do_bootm_linux
          • boot_prep_linux
          • params全局变量
          • boot_jump_linux
        • 当前images中的值
    • 优化启动时间

如何引导内核

uboot启动命令

  • 从Flash/TFTP加载uImage(zImage、Image),根文件系统、设备树dtb到SDRAM
mmc read image_addr part_off part_size
mmc read ramdisk_addr part_off part_size
mmc read dtb_addr part_off part_size
tftp uImage image_addr
tftp ramdisk ramdisk_addr
tftp dtb dtb_addr
  • 执行bootm(bootz、booti)命令,由内核接管控制权
#bootm/bootz/booti [image_addr] [ramdisk_addr] [dtb_addr]
#任何一项都可以没有,如果没有,用 “-’代替
用法1:bootm                              	#使用默认的镜像地址启动
用法2:bootm image_addr                   	#指定镜像地址启动。一般是uImage
用法3:bootm image_addr - dtb_addr  		#指定设备树地址

内核镜像介绍

# 编译内核时的链接过程
...
  LD      vmlinux
  SORTEX  vmlinux
  SYSMAP  System.map
  OBJCOPY arch/arm/boot/Image
  Kernel: arch/arm/boot/Image is ready
  SHIPPED arch/arm/boot/compressed/hyp-stub.S
  SHIPPED arch/arm/boot/compressed/fdt_rw.c
  SHIPPED arch/arm/boot/compressed/fdt.h
  SHIPPED arch/arm/boot/compressed/libfdt.h
  SHIPPED arch/arm/boot/compressed/libfdt_internal.h
  SHIPPED arch/arm/boot/compressed/fdt_ro.c
  SHIPPED arch/arm/boot/compressed/fdt_wip.c
  SHIPPED arch/arm/boot/compressed/fdt.c
  SHIPPED arch/arm/boot/compressed/lib1funcs.S
  SHIPPED arch/arm/boot/compressed/ashldi3.S
  SHIPPED arch/arm/boot/compressed/bswapsdi2.S
  LDS     arch/arm/boot/compressed/vmlinux.lds
  AS      arch/arm/boot/compressed/head.o
  GZIP    arch/arm/boot/compressed/piggy_data
  CC      arch/arm/boot/compressed/misc.o
  CC      arch/arm/boot/compressed/decompress.o
  CC      arch/arm/boot/compressed/string.o
  AS      arch/arm/boot/compressed/hyp-stub.o
  CC      arch/arm/boot/compressed/fdt_rw.o
  CC      arch/arm/boot/compressed/fdt_ro.o
  CC      arch/arm/boot/compressed/fdt_wip.o
  CC      arch/arm/boot/compressed/fdt.o
  CC      arch/arm/boot/compressed/atags_to_fdt.o
  AS      arch/arm/boot/compressed/lib1funcs.o
  AS      arch/arm/boot/compressed/ashldi3.o
  AS      arch/arm/boot/compressed/bswapsdi2.o
  AS      arch/arm/boot/compressed/piggy.o
  LD      arch/arm/boot/compressed/vmlinux
  OBJCOPY arch/arm/boot/zImage
  Kernel: arch/arm/boot/zImage is ready
# 第一步生成未经过压缩的vmlinux、Image
## vmlinux ---OBJCOPY---> arch/arm/boot/Image
# 第二步生成压缩的vmlinux、zImage
## arch/arm/boot/compressed/vmlinux ---OBJCOPY ---> arch/arm/boot/zImage

vmlinux:Linux内核编译生成原始内核文件,ELF格式,该映像可用于定位内核问题(readelf -s vmlinux),但不能直接引导Linux系统启动。

Image :使用objcopy处理vmlinux后( 去除其中的符号和重定位信息等 )生成的二进制内核映像。该映像未压缩,可直接引导Linux系统启动。

zImage:普通的压缩内核映像文件,使用gzip压缩Image后生成的Linux内核映像。

uImage:使用工具mkimage对普通的压缩内核映像文件(zImage)加工而得。它是uboot专用的映像文件,它是在zImage之前加上一个长度为64字节的头,说明这个内核的版本、加载位置、生成时间、大小等信息。

# path:u-boot-2019.04/tool/mkimage
#      linux-4.19.91/scripts/mkuboot.sh
# 帮助文档 u-boot-2019.04/docmkimage.1
Usage: ./mkimage -l image
          -l ==> list image header information
       ./mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
          -A ==> set architecture to 'arch' 		#指定CPU的体系结构 arm x86 
          -O ==> set operating system to 'os'		#指定操作系统类型 linux openbsd
          -T ==> set image type to 'type'			#指定映象类型 kernel、ramdisk
          -C ==> set compression type 'comp'		#指定映象压缩方式 none gzip 
          -a ==> set load address to 'addr' (hex)	#指定映象在内存中的加载地址
          -e ==> set entry point to 'ep' (hex)		#指定映象运行的入口点地址,addr+0x40(头部长度)
          -n ==> set image name to 'name'			#指定映象名
          -d ==> use image data from 'datafile'		#指定制作映象的源文件
          -x ==> set XIP (execute in place)

workspace@: ./mkuboot.sh -A arm -O linux -T kernel -C none -a 0x8000 -e 0x8000 -n 'my_kernel' -d ../arch/arm/boot/zImage uImage
Image Name:   my_kernel
Created:      Mon Mar 14 14:46:14 2022
Image Type:   ARM Linux Kernel Image (uncompressed)
Data Size:    1882944 Bytes = 1838.81 kB = 1.80 MB
Load Address: 00008000
Entry Point:  00008000

image header
mkuboot.sh -l uImage
在这里插入图片描述

hexdump -C uImage | less
在这里插入图片描述

// path:u-boot-2019.04/include/image.h
/*
 * Legacy format image header,
 * all data in network byte order (aka natural aka bigendian).
 */
typedef struct image_header {
	__be32		ih_magic;	/* Image Header Magic Number(镜像头部幻数为 #define IH_MAGIC    0x27051956)	*/
	__be32		ih_hcrc;	/* Image Header CRC Checksum(镜像头部CRC校验码)	*/	
	__be32		ih_time;	/* Image Creation Timestamp(镜像创建时间戳)	*/
	__be32		ih_size;	/* Image Data Size(镜像数据大小(不算头部))1882944 Bytes		*/
	__be32		ih_load;	/* Data	 Load  Address(镜像数据将要载入的内存地址)0x8000		*/
	__be32		ih_ep;		/* Entry Point Address(镜像入口地址)0x8000		*/
	__be32		ih_dcrc;	/* Image Data CRC Checksum(镜像数据CRC校验码)	*/		
	uint8_t		ih_os;		/* Operating System(操作系统类型)05 IH_OS_LINUX		*/
	uint8_t		ih_arch;	/* CPU architecture(CPU架构)02 IH_ARCH_ARM		*/
	uint8_t		ih_type;	/* Image Type(镜像类型)02 IH_TYPE_KERNEL			*/
	uint8_t		ih_comp;	/* Compression Type(压缩类型)00 IH_COMP_NONE		*/
	uint8_t		ih_name[IH_NMLEN];	/* Image Name(镜像名字ih_name,共32字节 #define IH_NMLEN 32 'my_kernel')  */
} image_header_t;

内核启动前提

参考linux-4.19.148/Documentation/arm/Booting和linux-4.19.148/Documentation/arm64/booting.txt
arm架构处理器对linux内核启动之前环境的需求

  1. cpu 寄存器设置
    ARM:
    R0 = 0
    R1 = 板级 id
    R2 = 启动参数在内存中的起始地址
    ARM64:
    x0 = dtb在系统内存的物理地址
    x1 = 0 保留给以后使用
    x2 = 0 保留给以后使用
    x3 = 0 保留给以后使用

  2. cpu 模式
    禁止所有中断
    ARM:必须为SVC(超级用户)模式
    ARM64:处于EL2或者非安全模式的EL1模式中

  3. 缓存、MMU
    关闭 MMU
    指令缓存可以开启或者关闭
    数据缓存必须关闭并且不能包含任何脏数据

  4. DMA 设备应当停止工作

  5. bootloader需要跳转到内核镜像的第一条指令处

  6. ARM64下系统寄存器设置
    内核镜像将要进入的异常级别(EL)前,必须在其更高的异常级别(EL)进行初始化,以防止在一个未知的状态执行。

Bootm命令详解

bootm要做的事情

a. 读取头部,把内核拷贝到合适的地方(bootm_headers_t images)

b. 将启动参数给内核准备好,并告诉内核参数的首地址

c. 设置cpu寄存器,禁止中断,关闭MMU和cache

d. 跳转到内核的入口地址,kernel开始运行

Bootm命令格式

#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, _usage,	\
				  _help, _comp)				\
		{ #_name, _maxargs, _rep, 0 ? _cmd : NULL, _usage,	\
			_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
//name:命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串
//maxargs:命令的最大参数个数
//repeatable:是否自动重复(按Enter键是否会重复执行)
//command:该命令对应的响应函数指针
//usage:简短的使用说明(字符串)
//help:较详细的使用说明(字符串)

U_BOOT_CMD(
	bootm,	CONFIG_SYS_MAXARGS,	1,	do_bootm,
	"boot application image from memory", bootm_help_text
);
Usage:
bootm [addr [arg ...]]
    - boot application image stored in memory
        passing arguments 'arg ...'; when booting a Linux kernel,
        'arg' can be the address of an initrd image
        When booting a Linux kernel which requires a flat device-tree
        a third argument is required which is the address of the
        device-tree blob. To boot that kernel without an initrd image,
        use a '-' for the second argument. If you do not pass a third
        a bd_info struct will be passed instead

Sub-commands to do part of the bootm sequence.  The sub-commands must be
issued in the order below (it's ok to not issue all sub-commands):
        start [addr [arg ...]]
        loados  - load OS image
        ramdisk - relocate initrd, set env initrd_start/initrd_end
        fdt     - relocate flat device tree
        cmdline - OS specific command line processing/setup
        bdt     - OS specific bd_t processing
        prep    - OS specific prep before relocation or go
        go      - start OS

#example
bootm										# 使用默认的镜像地址启动
bootm image_addr 							# 指定镜像地址
bootm image_addr - dtb_addr					# 同时指定设备树dtb地址
bootm image_addr ramdisk_addr dtb_addr		# 指定ramdisk、dtb地址
bootm start [image_addr] [ramdisk_addr]	[dtb_addr]	 # 找到镜像
bootm loados							    # 加载镜像
bootm ramdisk								# 重新定位initrd,设置环境变量initrd_start/initrd_end
bootm fdt									# 重新定位设备树
bootm prep									# 跳转内核前的处理,设置启动参数TAGS
bootm go									# 跳转到内核入口地址

do_bootm

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	/* determine if we have a sub command */
	argc--; argv++;
	if (argc > 0) {
		char *endp;

		simple_strtoul(argv[0], &endp, 16);
		if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
			/* 执行子命令 */
			return do_bootm_subcommand(cmdtp, flag, argc, argv);
	}
	
    /* 执行bootm命令的指定状态 */
	return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
		BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
		BOOTM_STATE_LOADOS |
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
		BOOTM_STATE_RAMDISK |
#endif
		BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
		BOOTM_STATE_OS_GO, &images, 1);
}
do_bootm_subcommand
static cmd_tbl_t cmd_bootm_sub[] = {
	U_BOOT_CMD_MKENT(start, 0, 1, (void *)BOOTM_STATE_START, "", ""),
	U_BOOT_CMD_MKENT(loados, 0, 1, (void *)BOOTM_STATE_LOADOS, "", ""),
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
	U_BOOT_CMD_MKENT(ramdisk, 0, 1, (void *)BOOTM_STATE_RAMDISK, "", ""),
#endif
#ifdef CONFIG_OF_LIBFDT
	U_BOOT_CMD_MKENT(fdt, 0, 1, (void *)BOOTM_STATE_FDT, "", ""),
#endif
	U_BOOT_CMD_MKENT(cmdline, 0, 1, (void *)BOOTM_STATE_OS_CMDLINE, "", ""),
	U_BOOT_CMD_MKENT(bdt, 0, 1, (void *)BOOTM_STATE_OS_BD_T, "", ""),
	U_BOOT_CMD_MKENT(prep, 0, 1, (void *)BOOTM_STATE_OS_PREP, "", ""),
	U_BOOT_CMD_MKENT(fake, 0, 1, (void *)BOOTM_STATE_OS_FAKE_GO, "", ""),
	U_BOOT_CMD_MKENT(go, 0, 1, (void *)BOOTM_STATE_OS_GO, "", ""),
};

static int do_bootm_subcommand(cmd_tbl_t *cmdtp, int flag, int argc,
			char * const argv[])
{
	int ret = 0;
	long state;
	cmd_tbl_t *c;

	c = find_cmd_tbl(argv[0], &cmd_bootm_sub[0], ARRAY_SIZE(cmd_bootm_sub));
	argc--; argv++;

	if (c) {
		state = (long)c->cmd;
		if (state == BOOTM_STATE_START)
			state |= BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER;
	} else {
		/* Unrecognized command */
		return CMD_RET_USAGE;
	}

	if (((state & BOOTM_STATE_START) != BOOTM_STATE_START) &&
	    images.state >= state) {
		printf("Trying to execute a command out of order\n");
		return CMD_RET_USAGE;
	}

    /* 同样执行bootm命令的指定状态 */
	ret = do_bootm_states(cmdtp, flag, argc, argv, state, &images, 0);

	return ret;
}

无论是subcommand还是一般bootm命令,最终都是执行do_bootmd_states函数,区别入参state不同

命令 state
bootm [image_addr] [ramdisk_addr] [dtb_addr] BOOTM_STATE_START | BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER | BOOTM_STATE_LOADOS | BOOTM_STATE_RAMDISK | BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO
bootm start BOOTM_STATE_START | BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER
bootm loados BOOTM_STATE_LOADOS
bootm ramdisk BOOTM_STATE_RAMDISK
bootm fdt BOOTM_STATE_FDT
bootm prep BOOTM_STATE_OS_PREP
bootm go BOOTM_STATE_OS_GO
images全局变量

do_bootmd_states函数的其中一个参数是全局images,是引导内核的一个重要变量。

/* u-boot-2019.04/common/bootm.c */
bootm_headers_t images;		/* pointers to os/initrd/fdt images */

/* u-boot-2019.04/include/image.h */
/*
 * Legacy and FIT format headers used by do_bootm() and do_bootm_()
 * routines.
 */
typedef struct bootm_headers {
	/*
	 * Legacy os image header, if it is a multi component image
	 * then boot_get_ramdisk() and get_fdt() will attempt to get
	 * data from second and third component accordingly.
	 */
	image_header_t	*legacy_hdr_os;		/* image header pointer 镜像头指针*/
	image_header_t	legacy_hdr_os_copy;	/* header copy 镜像头部*/
	ulong		legacy_hdr_valid;		/* header是否有效 */

#ifndef USE_HOSTCC
	image_info_t	os;		/* os image info 镜像信息*/
    typedef struct image_info {
		ulong		start, end;		/* start/end of blob blob起始地址和结束地址*/
		ulong		image_start, image_len; /* start of image within blob, len of image 镜像开始地址和长度*/
		ulong		load;			/* load addr for the image image的加载地址*/
		uint8_t		comp, type, os;		/* compression, type of image, os type 压缩类型、镜像类型和操作系统类型*/
		uint8_t		arch;			/* CPU architecture 架构类型*/
	} image_info_t;
    
	ulong		ep;		/* entry point of OS 入口地址*/

	ulong		rd_start, rd_end;/* ramdisk start/end ramdisk的起始和结束地址*/

	char		*ft_addr;	/* flat dev tree address 设备树地址*/
	ulong		ft_len;		/* length of flat device tree 设备树长度*/

	ulong		initrd_start;	/* initrd内存根文件系统起始地址 */
	ulong		initrd_end;		/* initrd内存根文件系统结束地址 */
	ulong		cmdline_start;	/* cmdline命令行起始地址 */
	ulong		cmdline_end;	/* cmdline命令行结束地址 */
	bd_t		*kbd;
#endif

	int		verify;		/* getenv("verify")[0] != 'n' 是否要对image进行验证*/
    
#ifdef CONFIG_LMB
	struct lmb	lmb;		/* for memory mgmt 用来进行内存管理*/
#endif
} bootm_headers_t;

do_bootmd_states

/*
 * @param cmdtp		Pointer to bootm command table entry
 * @param flag		Command flags (CMD_FLAG_...)
 * @param argc		Number of subcommand arguments (0 = no arguments)
 * @param argv		Arguments
 * @param states	Mask containing states to run (BOOTM_STATE_...)
 * @param images	Image header information
 * @param boot_progress 1 to show boot progress, 0 to not do this
 */
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
		    int states, bootm_headers_t *images, int boot_progress)
/* 初始化image全局变量,设置bootm的内存 */
|--if (states & BOOTM_STATE_START)
| |--ret = bootm_start
/* find os 确定image的内存地址 */
|--if (!ret && (states & BOOTM_STATE_FINDOS))
| |--ret = bootm_find_os
/* find ramdisk、fdt 确定ramdisk、fdt的内存地址 */
|--if (!ret && (states & BOOTM_STATE_FINDOTHER))
| |--ret = bootm_find_other(cmdtp, flag, argc, argv);
/* Load the OS 加载image */
|--if (!ret && (states & BOOTM_STATE_LOADOS))
| |--ret = bootm_load_os
/* Relocate the ramdisk 重定位ramdisk */
|--if (!ret && (states & BOOTM_STATE_RAMDISK))
| |--ret = boot_ramdisk_high
/* Relocate the fdt 为设备树中的memory reserve预留内存,重定位fdt */
|--if (!ret && (states & BOOTM_STATE_FDT))
| |--boot_fdt_add_mem_rsv_regions
| |--ret = boot_relocate_fdt
/* From now on, we need the OS boot function */
/* get boot_fn 获取启动函数 */
|--boot_fn = bootm_os_get_boot_func(images->os.os)
/* boot_fn-BOOTM_STATE_OS_PREP 设置dtb或者atags */
|--if (!ret && (states & BOOTM_STATE_OS_PREP))
| |--ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
/* boot_fn-BOOTM_STATE_OS_GO 禁止中断,关闭cache,跳转到内核入口地址*/
|--if (!ret && (states & BOOTM_STATE_OS_GO))
| |--ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
				images, boot_fn);
| | |--boot_fn(state, argc, argv, images);
bootm_start
  • 初始化images全局变量

  • 根据环境变量verify设置images.verify = 0

  • 根据环境变量bootm_low和bootm_size设置images.lmb

    • setenv bootm_low = 0x0;

    • setenv bootm_size = 0x02000000

    images.lmb.memory.region[0].base = 0x0

    images.lmb.memory.region[0].size = 0x02000000

    images.lmb.reserved.region[0].base = 0x0

    images.lmb.reserved.region[0].size = 0x0

/* 若需要重定位fdt和ramdisk,则从该内存中进行申请 */
lmb->memory.region[0].base = bootm_low;
lmb->memory.region[0].size = bootm_size
lmb->memory.cnt = 1;
lmb->memory.size = 0;

/* nt98566上未设置reserved.region */
lmb->reserved.region[0].base = sp - 4096;
lmb->reserved.region[0].size = gd->bd->bi_dram[0].start + gd->bd->bi_dram[0].size - base;
lmb->reserved.cnt = 1;
lmb->reserved.size = 0;
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,
		       char * const argv[])
|--memset((void *)&images, 0, sizeof(images));
|--images.verify = getenv_yesno("verify");
/* 若定义了CONFIG_LMB,设置images.lmb,用于内存管理,否则为空函数 */
|--boot_start_lmb(&images);
|--images.state = BOOTM_STATE_START;
bootm_find_os

bootm 0x5800000 - 0x5c00000

  • 设置mages.os.image_start = 0x5800000 + 0x40 = 0x5800040
  • 设置images.os.image_len = 0x207c28
  • 将0x5800000上的0x40大小的头部拷贝给images->legacy_hdr_os_copy
  • 设置images->legacy_hdr_os = 0x5800000
  • 设置images->legacy_hdr_valid = 1;
  • 设置images.os.type = image_get_type(os_hdr) = IH_TYPE_KERNEL
  • 设置images.os.comp = image_get_comp(os_hdr) = IH_COMP_NONE
  • 设置images.os.os = image_get_os(os_hdr) = IH_OS_LINUX
  • 设置images.os.end = image_get_image_end(os_hdr) = 0x5800000 + 0x207c28+ 0x40 = 0x5a07c68
  • 设置images.os.load = image_get_load(os_hdr) = 0x8000
  • 设置images.os.arch = image_get_arch(os_hdr) = IH_ARCH_ARM
  • 设置images.ep = image_get_ep(&images.legacy_hdr_os_copy) = 0x8000
  • 设置images.os.start = 0x5800000
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
			 char * const argv[])
|--const void *os_hdr;  
/* get kernel image header, start address and length */
|--os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
			&images, &images.os.image_start, &images.os.image_len);
/* get image parameters */
|--switch (genimg_get_format(os_hdr)) {
| |--case IMAGE_FORMAT_LEGACY:
| | |--images.os.type = image_get_type(os_hdr);
| | |--images.os.comp = image_get_comp(os_hdr);
| | |--images.os.os = image_get_os(os_hdr);
| | |--images.os.end = image_get_image_end(os_hdr); 
| | |--images.os.load = image_get_load(os_hdr);
| | |--images.os.arch = image_get_arch(os_hdr);
|--} 
|--if (images.legacy_hdr_valid)
| |--images.ep = image_get_ep(&images.legacy_hdr_os_copy);  
|--images.os.start = map_to_sysmem(os_hdr);
boot_get_kernel
static const void *boot_get_kernel(cmd_tbl_t *cmdtp, int flag, int argc,
				   char * const argv[], bootm_headers_t *images,
				   ulong *os_data, ulong *os_len)
|--image_header_t	*hdr;
/* img_addr = simple_strtoul(argv[0], NULL, 16) */
|--ulong img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0],
					      &fit_uname_config,
					      &fit_uname_kernel);
|--const void *buf = map_sysmem(img_addr, 0);
|--switch (genimg_get_format(buf)){
| |--case IMAGE_FORMAT_LEGACY:
/* verify legacy format kernel image */
| | |--hdr = image_get_kernel(img_addr, images->verify);
/* get os_data and os_len */
| | |--switch (image_get_type(hdr)) {
| | | |--case IH_TYPE_KERNEL:
		 case IH_TYPE_KERNEL_NOLOAD:
        	*os_data = image_get_data(hdr);
        	*os_len = image_get_data_size(hdr);
| | |--}
/* copy image header to allow for image overwrites during kernel decompression.*/
| | |--memmove(&images->legacy_hdr_os_copy, hdr,
			sizeof(image_header_t));
| | |--images->legacy_hdr_os = hdr;
| | |--images->legacy_hdr_valid = 1;
|--}
|--return buf;
bootm_find_other

bootm 0x5800000 - 0x5c00000

  • 设置images.rd_start = 0
  • 设置images.rd_end = 0
  • 设置images.ft_addr = 0x5c00000
  • 设置images.ft_len= 0x45ea
static int bootm_find_other(cmd_tbl_t *cmdtp, int flag, int argc,
			    char * const argv[])
|--return bootm_find_images(flag, argc, argv); 
/* find ramdisk */
| |--ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
			       &images.rd_start, &images.rd_end);
/* find flattened device tree */
| |--ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,
			   &images.ft_addr, &images.ft_len);
| |--set_working_fdt_addr((ulong)images.ft_addr);
| | |--setenv_hex("fdtaddr", addr);
bootm_load_os
  • 解压内核镜像,这里指的是制作uImage时的压缩类型,当前为IH_COMP_NONE

  • 若images.os.load == mages.os.image_start,即bootm传入的image_addr+0x40等于制作uImage时指定的load地址,无需进行memmove

  • 若images.os.load != mages.os.image_start,则将mages.os.image_start上的image镜像拷贝到images.os.load地址上,长度为images.os.image_len

    当前images.os.load=0x8000, mages.os.image_start=0x5800040,需要进行镜像拷贝

if (!ret && (states & BOOTM_STATE_LOADOS))
/* 在准备加载/引导时禁用中断 */
|--ulong iflag = bootm_disable_interrupts()
|--ret = bootm_load_os(images, &load_end, 0);

static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
			 int boot_progress)
|--int err = image_decomp(os.comp, load, os.image_start, os.type,
				 load_buf, image_buf, image_len,
				 CONFIG_SYS_BOOTM_LEN, &load_end);
/*
 * images.os.load=0x8000,  mages.os.image_start=0x5800040
 * images.os.image_len = 0x207c28, CONFIG_SYS_BOOTM_LEN=0x1900000
 */
| |--if (load == os.image_start)
| | |--break
| |--if (image_len <= CONFIG_SYS_BOOTM_LEN)
/* 将0x5800040上的镜像拷贝到0x8000,拷贝长度为0x207c28 */
| | |--memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);    

/* 刷新load范围的d-cache/统一缓存 */
|--flush_cache(load, ALIGN(load_end - load, ARCH_DMA_MINALIGN));
/* 将images->os.load到oad_end的这片内存保留 */
|--lmb_reserve(&images->lmb, images->os.load, (load_end -
						    images->os.load));
boot_ramdisk_high
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
ulong rd_len = images->rd_end - images->rd_start;
int ret = boot_ramdisk_high(&images->lmb, images->rd_start,
			rd_len, &images->initrd_start, &images->initrd_end);
if (!ret) {
			setenv_hex("initrd_start", images->initrd_start);
			setenv_hex("initrd_end", images->initrd_end);
		}
#endif

int boot_ramdisk_high(struct lmb *lmb, ulong rd_data, ulong rd_len,
		  ulong *initrd_start, ulong *initrd_end)
|--if ((s = getenv("initrd_high")) != NULL)
| |--ulong initrd_high = simple_strtoul(s, NULL, 16);
| |--if (initrd_high == ~0)
| | |--initrd_copy_to_ram = 0;
|--else
| |--initrd_high = getenv_bootm_mapsize() + getenv_bootm_low();

|--if (rd_data) 
| |--if (!initrd_copy_to_ram)
| |--*initrd_start = rd_data;
| |--*initrd_end = rd_data + rd_len;
| |--lmb_reserve(lmb, rd_data, rd_len);
|--else
| |--if (initrd_high)    
| | |--*initrd_start = (ulong)lmb_alloc_base(lmb,
						rd_len, 0x1000, initrd_high);
| |--else
| | |--*initrd_start = (ulong)lmb_alloc(lmb, rd_len,
								 0x1000);
| |--*initrd_end = *initrd_start + rd_len;
| |--memmove_wd((void *)*initrd_start,
					(void *)rd_data, rd_len, CHUNKSZ);
|--else
| |--*initrd_start = 0;
| |--*initrd_end = 0;

bootm指定ramdisk_addr

  • setenv initrd_high 不等于~0

    从lmb.memory.region[0]中申请内存,且内存不高于initrd_high,并拷贝镜像

    images->initrd_start = (ulong)lmb_alloc_base(lmb, rd_len, 0x1000, initrd_high);

    images->initrd_end = images->initrd_start + images->rd_end - images->rd_start;

    memmove_wd(void *)*initrd_start, (void *)rd_data, rd_len, CHUNKSZ)

  • setenv initrd_high 0xffffffff,不进行reloacate,保留这块内存

    images->initrd_start = images->rd_start

    images->initrd_end = images->rd_end

    lmb_reserve(lmb, rd_data, rd_len)

  • setenv initrd_high 0

    从lmb.memory.region[0]中任意位置申请内存,并拷贝镜像

    images->initrd_start = (ulong)lmb_alloc(lmb, rd_len, 0x1000);

    mages->initrd_end = images->initrd_start + images->rd_end - images->rd_start;

    memmove_wd(void *)*initrd_start, (void *)rd_data, rd_len, CHUNKSZ)

  • 没有环境变量initrd_high

    initrd_high = bootm_mapsize + bootm_low

nt98566上,bootm未指定ramdisk_addr

images->initrd_start = 0

images->initrd_end = 0

boot_relocate_fdt
/* 只有执行bootm start、bootm fdt子命令才生效 */
#if IMAGE_ENABLE_OF_LIBFDT && defined(CONFIG_LMB)
	if (!ret && (states & BOOTM_STATE_FDT)) {
  		/* 为设备树中的memory reserve预留内存 */
		boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
		ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
					&images->ft_len);
	}
#endif

int boot_relocate_fdt(struct lmb *lmb, char **of_flat_tree, ulong *of_size) 
|--void	*fdt_blob = *of_flat_tree;
|--ulong of_len = *of_size + CONFIG_SYS_FDT_PAD;
|--char	*fdt_high = getenv("fdt_high") 
|--if (fdt_high)
| |--void *desired_addr = (void *)simple_strtoul(fdt_high, NULL, 16);
| |--if (((ulong) desired_addr) == ~0UL)
| | |--void	*of_start = fdt_blob;
| | |--lmb_reserve(lmb, (ulong)of_start, of_len);
| | |--int	disable_relocation = 1
| |--else if (desired_addr)
| | |--of_start = (void *)(ulong) lmb_alloc_base(lmb, of_len, 0x1000,
							   (ulong)desired_addr);
| |--else 
| | |--of_start = (void *)(ulong) lmb_alloc(lmb, of_len, 0x1000); 
|--else
| |--of_start = (void *)(ulong) lmb_alloc_base(lmb, of_len, 0x1000,
						   getenv_bootm_mapsize() + getenv_bootm_low());

|--if (disable_relocation)
/* 设置设备树,并进行镜像拷贝 */
| |--fdt_set_totalsize(of_start, of_len);
|--else
| |--int err = fdt_open_into(fdt_blob, of_start, of_len)

|--*of_flat_tree = of_start;
|--*of_size = of_len;
|--set_working_fdt_addr((ulong)*of_flat_tree);
| |--setenv_hex("fdtaddr", addr);
  • setenv fdt_high 不等于~0

    从lmb.memory.region[0]中申请内存,且内存不高于initrd_high,并拷贝镜像

    images->ft_addr = (void *)(ulong) lmb_alloc_base(lmb, of_len, 0x1000, (ulong)initrd_high);

    images->ft_len += CONFIG_SYS_FDT_PAD

    memmove

  • setenv fdt_high 0xffffffff, 不进行reloacate,保留这块内存

    images->ft_addr 即bootm 传入的dtb_addr

    images->ft_len += CONFIG_SYS_FDT_PAD

    lmb_reserve

  • setenv fdt_high 0

    从lmb.memory.region[0]中任意位置申请内存

    mages->ft_addr = (void *)(ulong) lmb_alloc(lmb, of_len, 0x1000);

    images->ft_len += CONFIG_SYS_FDT_PAD

  • 没有环境变量fdt_high

    fdt_high = bootm_mapsize + bootm_low

bootm_os_get_boot_func
  • 根据images->os.os获取对应的boot_os_fn,当前images->os.os为IH_OS_LINUX,boot_fn为do_bootm_linux
images->os.os = IH_OS_LINUX
boot_fn = bootm_os_get_boot_func(images->os.os)

static boot_os_fn *boot_os[] = {
	[IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
	[IH_OS_LINUX] = do_bootm_linux,
#endif
    /* ... */
};
    
boot_os_fn *bootm_os_get_boot_func(int os)
|--return boot_os[os];
do_bootm_linux
  • 执行boot_fn(BOOTM_STATE_OS_PREP,argc, argv, images
  • 执行boot_fn(BOOTM_STATE_OS_GO, argc, argv, images)
if (!ret && (states & BOOTM_STATE_OS_PREP))
|--ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_GO))
|--ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
				images, boot_fn);
| |--boot_fn(state, argc, argv, images);

int do_bootm_linux(int flag, int argc, char * const argv[],
		   bootm_headers_t *images)
|--if (flag & BOOTM_STATE_OS_PREP)
| |--boot_prep_linux(images);  
| |--return 0;
|--boot_prep_linux(images);
|--boot_jump_linux(images, flag);
|--return 0;
boot_prep_linux
  • 设置FDT

    • 为设备树中的reserved-memory预留内存,nt98566中为cma(连续的内存分配器)预留内存,地址为0x01a00000,长度为0
      Bootm启动流程分析_第1张图片

    • 重定位设备树,见boot_relocate_fdt小节

      setenv fdt_high 0xffffffff

      bootm 0x5800000 - 0x5c00000

      mages->ft_addr=0x5c00000

      images->ft_len = 0x45ea /* 区别bootm fdt命令,正常启动不修改其长度(可能是代码bug,此变量在后续启动流程中无用) */

    • 修改设备树,取代传统的TAG

  • 设置ATAGS,从gd->bd->bi_boot_params开始设置要传给内核的TAG信息

static void boot_prep_linux(bootm_headers_t *images)
|--char *commandline = getenv("bootargs");
/* USE FDT,将uboot要向kernel传递的信息修改到设备树, 并将R2寄存器指向该设备树地址*/
|--if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
| |--image_setup_linux(images)
/* 为设备树中的memory reserve预留内存 */
| | |--boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);
/* 从lmb.memory.region[0]中申请内存以存放bootargs */
| | |--boot_get_cmdline(lmb, &images->cmdline_start, &images->cmdline_end);
/* 重定位设备树 */
| | |--ret = boot_relocate_fdt(lmb, of_flat_tree, &of_size);
/* 修改设备树,取代传统的TAG */
| | |--ret = image_setup_libfdt(images, *of_flat_tree, of_size, lmb);
| | | |--fdt_root(blob)     /* 修改序列号serial-number */
| | | |--fdt_chosen(blob)	/* 传递bootargs到chosen节点 */
| | | |--arch_fixup_fdt(blob) /* gd->bd->bi_dram[bank]修改设备树中的memory节点 */
| | | |--fdt_fixup_ethernet(blob);    /* 修改设备树中的mac-address */
| | | |--ft_board_setup(blob, gd->bd); /* 空 */
| | | |--ft_system_setup(blob, gd->bd); /* 空 */
| | | |--fdt_shrink_to_minimum(blob, 0); /* 压缩设备树体积 */
| | | |--lmb_reserve(lmb, (ulong)blob, of_size); /* 重新为设备树保留内存 */
| | | |--fdt_initrd(blob, *initrd_start, *initrd_end); /* 设置chosen中的linux,initrd-start和linux,initrd-end */

/* USE ATAGS */
/* tag类型的结构体指针params最初指向bd->bi_boot_params,连续向上偏移hdr.size,直到setup_end_tag,同设备树传递的信息,只是将这些信息放到bd->bi_boot_params地址上,并将R2寄存器指向该地址 */
|--else if (BOOTM_ENABLE_TAGS)
| |--setup_start_tag(gd->bd);
| | |--params = (struct tag *)bd->bi_boot_params;
| | |--params = tag_next (params);
| |--setup_serial_tag(&params);
| | |--params->u.serialnr.low = serialnr.low;
| | |--params->u.serialnr.high= serialnr.high;
| | |--params = tag_next (params);
| |--setup_commandline_tag(gd->bd, commandline);
| |--setup_revision_tag(&params);
| |--setup_memory_tags(gd->bd);
| |--setup_initrd_tag(gd->bd, images->initrd_start, images->initrd_end);
| |--setup_initrd_tag(gd->bd, images->rd_start, images->rd_end);
| |--setup_board_tags(&params);
| |--setup_end_tag(gd->bd);
params全局变量
/* u-boot-2019.04/arch/arm/lib/bootm.c */
static struct tag *params;

/* arch/arm/include/asm/setup.h */
struct tag_header {
	u32 size;
	u32 tag;
};

struct tag_core {
	u32 flags;		/* bit 0 = read-only */
	u32 pagesize;
	u32 rootdev;
};

struct tag_mem32 {
	u32	size;
	u32	start;	/* physical start address */
};
/* ... */

struct tag {
	struct tag_header hdr;
	union {
		struct tag_core		core;
		struct tag_mem32	mem;
		struct tag_videotext	videotext;
		struct tag_ramdisk	ramdisk;
		struct tag_initrd	initrd;
		struct tag_serialnr	serialnr;
		struct tag_revision	revision;
		struct tag_videolfb	videolfb;
		struct tag_cmdline	cmdline;
		/*
		 * Acorn specific
		 */
		struct tag_acorn	acorn;
		/*
		 * DC21285 specific
		 */
		struct tag_memclk	memclk;
	} u;
};
boot_jump_linux
  • 禁止中断,关闭cache,关闭mmu

  • 跳转到内核的入口地址images->ep

    #ifdef CONFIG_ARM64

    • CONFIG_ARMV8_SWITCH_TO_EL1:

    armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0, (u64)switch_to_el1, ES_TO_AARCH64);

    • 32-bit OS

    armv8_switch_to_el2(0, (u64)gd->bd->bi_arch_number, (u64)images->ft_addr, 0, (u64)images->ep, ES_TO_AARCH32);

    • 64-bit OS

    armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0, images->ep, ES_TO_AARCH64);

    #else

    kernel_entry = (void (*)(int, int, uint))images->ep;

    machid = gd->bd->bi_arch_numberstrict_strtoul(env_get("machid"), 16, &machid)

    FDT: r2 = (unsigned long)images->ft_addr;

    ATAGS: r2 = gd->bd->bi_boot_params;

    kernel_entry(0, machid, r2);

static void boot_jump_linux(bootm_headers_t *images, int flag)

/* ARM64 */
#ifdef CONFIG_ARM64
|--void (*kernel_entry)(void *fdt_addr, void *res0, void *res1, void *res2);
|--kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
				void *res2))images->ep;
|--announce_and_cleanup(fake);
/* 禁止中断,关闭cache,关mmu */
| |--cleanup_before_linux();
| | |--disable_interrupts();
| | |--dcache_disable();
| | |--icache_disable();
| | |--invalidate_icache_all();
| | |--cpu_cache_initialization
/* flush cache before swtiching to EL2 */
|--do_nonsec_virt_switch();
| |--smp_kick_all_cpus();
| |--dcache_disable();
	 #ifdef CONFIG_ARMV8_SWITCH_TO_EL1
| |--armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,
				    (u64)switch_to_el1, ES_TO_AARCH64);
     #else
| |--if ((IH_ARCH_DEFAULT == IH_ARCH_ARM64) &&
		    (images->os.arch == IH_ARCH_ARM))
| | |--armv8_switch_to_el2(0, (u64)gd->bd->bi_arch_number,
					    (u64)images->ft_addr, 0,
					    (u64)images->ep,
					    ES_TO_AARCH32);
| |--else
| | |--armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,
					    images->ep,
					    ES_TO_AARCH64);    
	#endif


/* ARM */
#else
|--unsigned long machid = gd->bd->bi_arch_number;
|--void (*kernel_entry)(int zero, int arch, uint params);
|--kernel_entry = (void (*)(int, int, uint))images->ep;
|--char *s = getenv("machid");
| |--strict_strtoul(s, 16, &machid)
|--announce_and_cleanup(fake);
|--if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
| |--unsigned long r2 = (unsigned long)images->ft_addr;
|--else
| |--r2 = gd->bd->bi_boot_params;
|--kernel_entry(0, machid, r2);   
当前images中的值

以NT98566平台为例:

  • 片内SDRAM 128M
  • setenv bootm_low = 0x0;
  • setenv bootm_size = 0x02000000
  • setenv bootm_mapsize = 0x02000000
  • setenv fdt_high = 0xffffffff
  • bootm 0x5800000 - 0x5c00000
成员
image_header_t *legacy_hdr_os; 0x5800000
image_header_t legacy_hdr_os_copy; memmove(&images->legacy_hdr_os_copy, 0x5800000, sizeof(image_header_t));
ulong legacy_hdr_valid; 1
ulong os.start 0x5800000
ulong os.end 0x5a07c68
ulong os.image_start 0x5800040
ulong os.image_len 0x207c28
ulong os.load 0x8000
uint8_t os.comp 00 IH_COMP_NONE
uint8_t os.type 02 IH_TYPE_KERNEL
uint8_t os.os 05 IH_OS_LINUX
uint8_t os.arch 02 IH_ARCH_ARM
ulong ep 0x8000
ulong rd_start 0
ulong rd_end 0
char *ft_addr 0x5c00000
ulong ft_len 0x45ea
ulong initrd_start 0
ulong initrd_end 0
ulong cmdline_start 0
ulong cmdline_end 0
bd_t *kbd 0
int verify 0
int state; BOOTM_STATE_START
lmb.memory.cnt 1
lmb.memory.size 0
lmb.memory.region[0].base 0x0
lmb.memory.region[0].size 0x2000000
lmb.reserved.cnt 3
lmb.reserved.size 0
lmb.reserved.region[0].base 0x8000 /* 为image镜像预留内存 */
lmb.reserved.region[0].size 0x207c28 /* image镜像大小 */
lmb.reserved.region[1].base 1a00000 /* 设备树中reserved-memory */
lmb.reserved.region[2].size 0
lmb.reserved.region[0].base 5c00000 /* 为设备树预留内存 */
lmb.reserved.region[0].size 0x5000 /* 设备树执行fdt_shrink_to_minimum(blob, 0)后压缩过的大小 */

优化启动时间

  • 关闭image的内容打印、加载信息
/* 例如注释以下函数 */
image_print_contents
  • 若直接从flash分区加载image镜像,可以先读0x40(具体根据flash的block大小确定)字节头部,从头部获取image镜像大小,再加载image镜像,避免读取整块flash分区
sprintf(cmd, "mmc read 0x%x 0x%llx 0x%x", image_addr, part_off, 1)
run_command(cmd, 0)
hdr = (image_header_t *)image_addr;
size = image_get_data_size(hdr) + sizeof(image_header_t);
align_size = ALIGN_CEIL(size, MMC_MAX_BLOCK_LEN) / MMC_MAX_BLOCK_LEN;
sprintf(cmd, "mmc read 0x%x 0x%llx 0x%x", image_addr, part_off, align_size);
  • 设置环境变量verify=n,不对image镜像的数据进行crc校验,甚至可以只对image进行magic校验
bootm_find_os
|--boot_get_kernel
| |--image_get_kernel
| | |--image_check_magic(hdr)
| | |--image_check_hcrc(hdr)
| | |--if (verify)
| | | |--image_check_dcrc(hdr)
| | |--image_check_target_arch(hdr)
    
    
bootm_find_other
|--bootm_find_images
| |--boot_get_ramdisk 
| | |--image_get_ramdisk
| | | |--image_check_magic
| | | |--image_check_hcrc
| | | |--if (verify)
| | | | |--image_check_dcrc(rd_hdr)    
  • 若制作uImage时指定的-e参数,即镜像入口地址为ep,bootm命令传入的镜像地址为image_addr,保持:ep = image_addr + 0x40(sizeof(image_header_t)),以节省image镜像的拷贝时间
bootm_load_os
|--image_decomp

| |--if (load == os.image_start)
| | |--break
| |--if (image_len <= CONFIG_SYS_BOOTM_LEN)
/* 将0x5800040上的镜像拷贝到0x8000,拷贝长度为0x1cbb40 */
| | |--memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);  
  • 设置setenv fdt_high = 0xffffffff,setenv initrd_high = 0xffffffff,不进行根文件系统和设备树的重定位,节省拷贝时间,参考boot_ramdisk_high和boot_relocate_fdt小节
  • 注释掉uboot中对设备树无用的信息传递,找出设备树节点需要耗时
image_setup_libfdt
|--fdt_root(blob)     /* 修改序列号serial-number */
|--arch_fixup_fdt(blob) /* gd->bd->bi_dram[bank]修改设备树中的memory节点 */
|--fdt_fixup_ethernet(blob);    /* 修改设备树中的mac-address */
|--ft_board_setup(blob, gd->bd); /* 空 */
|--ft_system_setup(blob, gd->bd); /* 空 */
|--fdt_initrd(blob, *initrd_start, *initrd_end); /* 设置chosen中的linux,initrd-start和linux,initrd-end */
  

你可能感兴趣的:(uboot,linux)