锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

2021年Linux技术总结(一):U-boot

时间:2023-12-29 14:37:06 fit电源连接器系列

一、U-boot简介

Linux系统的启动必须需要一个bootloader程序(相当于windows的BIOS),bootloader目的是复制操作系统的图像文件RAM中去,然后跳到它的入口处执行。

U-boot是常用的bootloader该程序主要用于嵌入式系统的引导加载程序,可以支持包括PPC、ARM、AVR32、MIPS、x86、68k、Nios与MicroBlaze,是一套在GNU在通用公共许可证下发布的自由软件。
U-boot官网:http://www.denx.de/wiki/U-Boot/WebHome,但建议使用半导体制造商自己的官方网站进行维护U-boot引导程序

二、U-boot启动流程

U-boot了解启动过程需要一定的汇编基础。以下是从左到右、从上到下制作的思维导图。
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-mdx6JhCp-1647230851933)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220119162709314.png)]

整个U-boot启动过程主要包括:

? 1. 链接脚本进入入口函数;

? 2. 初始化arm设置异常向量表的地址;

? 3. 设置处理器(和协处理器)模式;

? 4. 板材初始化:
? (1)DDR初始化;
? (2)时钟系统初始化;
? (3)将操作系统、设备树、虚拟文件系统从拟文件系统DDR中;
? (4)初始化串口;

? 5. 设置参数并跳转到操作系统;

下一章将介绍详细的启动过程。

2.1 U-boot入口

要了解系统的启动过程,首先要找到其入口函数,从源头上分析系统的启动过程,U-boot的启动流程要从它的编译脚本开始找入口函数。入口函数主要完成的工作是:

  • 初始化arm设置异常向量表的地址;
  • 设置处理器(和协处理器)模式;
  • 向量表重定位
  • 配置CP15
  • 内存初始化

移植前,确定移植源开发板,找到其编译脚本,但U-boot编译前链接脚本不完整,需要编译,编译后会U-boot链接脚本出现在根目录下:u-boot.lds文件(lds语法参考lds官方文档),连接脚本内容如下(不同板脚本内容可能不同):

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS {      . = 0x00000000;      . = ALIGN(4);      .text :      {           *(.__image_copy_start)           *(.vectors)           arch/arm/cpu/armv7/start.o (.text*)       *(.text*) } . = ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } . = ALIGN(4); .data : {    *(.data*) }  . = ALIGN(4);  . = .;  . = ALIGN(4);  .u_boot_list : {   KEEP(*(SORT(.u_boot_list*)));  }  . = ALIGN(4);  .image_copy_end :  {   *(.__image_copy_end)  }  .rel_dyn_start :  {   *(.__rel_dyn_start)  }  .rel.dyn : {   *(.rel*)  }  .rel_dyn_end :  {   *(.__rel_dyn_end)  }  .end :  {   *(.__end)  }  _image_binary_end = .;  . = ALIGN(4096);  .mmutable : {   *(.mmutable)  }  .bss_start __rel_dyn_start (OVERLAY) : {   KEEP(*(.__bss_start));   __bss_base = .;  }  .bss __bss_base (OVERLAY) : {   *(.bss*)    . = ALIGN(4);    __bss_limit = .;  }  .bss_end __bss_limit (OVERLAY) : {   KEEP(*(.__bss_end));  }  .dynsym _image_binary_end : { *(.dynsym) }  .dynbss : { *(.dynbss) }  .dynstr : { *(.dynstr*) }  .dynamic : { *(.dynamic*) }  .plt : { *(.plt*) }  .interp : { *(.interp*) }  .gnu.hash : { *(.gnu.hash) }  .gnu : { *(.gnu*) }  .ARM.exidx : { *(.ARM.exidx*) }  .gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) } } 

ENTRY(_star)就是U-boot该函数定义为入口函数arch/arm/lib/vectors.S文件是通用的ARM主要用于异常代码:

  • 初始化arm异常向量表
  • 设置异常向量表的地址。

连接脚本SECTIONS函数(第8行)中,可以看出来,起始代码是vectors,文件代码如下:

SECTIONS { 
          . = 0x00000000;  . = ALIGN(4);  .text :  { 
           *(.__image_copy_start)   *(.vectors)   arch/arm/cpu/armv7/start.o (.text*)   *(.text*)  } 

内存中的入口函数地址可以在U-boot.map(也可在根目录下查看):

 *(.vectors)  .vectors       0x000000008780000      0x300 arch/arm/lib/built-in.o

​ 通过连接脚本,我们知道了入口函数:_start,下面看一下它的内容:

/* ************************************************************************* * Exception vectors as described in ARM reference manuals * Uses indirect branch to allow reaching handlers anywhere in memory. ************************************************************************* */
_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
	.word	CONFIG_SYS_DV_NOR_BOOT_CFG
#endif

	b	reset
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq
	ldr	pc, _fiq

​ _start函数会在第13行这里转跳到reset函数,这个函数在arch/arm/cpu/armv7/statr.S(链接脚本11行得知)。reset函数又转跳到save_boot_params函数,然后又转跳到save_boot_params_ret函数,对处理器工作模式进行设置。

reset:
	/* Allow the board to save important registers */
	b	save_boot_params

--------------------------------------------------------------------------------------
/************************************************************************* * * void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3) * __attribute__((weak)); * Stack pointer is not yet initialized at this moment * Don't save anything to stack even if compiled with -O0 * *************************************************************************/
ENTRY(save_boot_params)
	b	save_boot_params_ret		@ back to my caller	

--------------------------------------------------------------------------------------
save_boot_params_ret:
	/* * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode, * except if in HYP mode already */
	mrs	r0, cpsr
	and	r1, r0, #0x1f		@ mask mode bits
	teq	r1, #0x1a		@ test for HYP mode
	bicne	r0, r0, #0x1f		@ clear all mode bits
	orrne	r0, r0, #0x13		@ set SVC mode
	orr	r0, r0, #0xc0		@ disable FIQ and IRQ
	msr	cpsr,r0

​ 处理器模式配置完成后,进行行 “向量表重定位” 的设置,配置函数就在save_boot_params_ret:下方:

#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
	/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
	mrc	p15, 0, r0, c1, c0, 0	@ Read CP15 SCTLR Register
	bic	r0, #CR_V		@ V = 0
	mcr	p15, 0, r0, c1, c0, 0	@ Write CP15 SCTLR Register

	/* Set vector address in CP15 VBAR register */
	ldr	r0, =_start
	mcr	p15, 0, r0, c12, c0, 0	@Set VBAR
#endif

​ 向量表重定位完成后将会转跳到函数:cpu_init_cp15 对CP15进行配置,并进入函数 cpu_init_crit 转跳到函数:lowlevel_init

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_cp15
	bl	cpu_init_crit
#endif
--------------------------------------------------------------------------
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
/************************************************************************* * CPU_init_critical registers * setup important registers * setup memory timing *************************************************************************/
ENTRY(cpu_init_crit)
	/* * Jump to board specific initialization... * The Mask ROM will have already initialized * basic memory. Go here to bump up clock rate and handle * wake up conditions. */
	b	lowlevel_init		@ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
#endif

​ 函数 lowlevel_init 定义在同级目录下的lowlevel_init.S中,主要是完成对内存的初始化,内容如下(原文注释删了):

ENTRY(lowlevel_init)
	ldr	sp, =CONFIG_SYS_INIT_SP_ADDR
	bic	sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
	mov	r9, #0
#else
#ifdef CONFIG_SPL_BUILD
	ldr	r9, =gdata
#else
	sub	sp, sp, #GD_SIZE
	bic	sp, sp, #7
	mov	r9, sp
#endif
#endif
	push	{ 
        ip, lr}
	bl	s_init
	pop	{ 
        ip, pc}
ENDPROC(lowlevel_init)

2.2 U-boot初始化

​ U-boot初始化部分主要完成以下工作:

  • (1)时钟系统等必要外设初始化;
  • (2)内存分配;
  • (2)将U-boot自身移动到DDR;
  • (3)向量表重定位;
  • (3)初始化其它外设;

​ 内存初始化完成后,再回到start.S,在函数cpu_init_crit下面,还有一句:bl _main,这句代码会转跳到main函数。main 函数定义在arch/arm/lib/ crt0.S 中,下面是对原代码头部注释进行了一个翻译,可以大致了解一下main的功能:

这个文件处理U-Boot启动过程中与目标无关的阶段,其中需要C运行时环境。它的入口点是_main,start.S文件的分支。_main执行顺序为:
1、设置调用board_init_f()的初始环境。这个环境只提供一个堆栈和一个存储GD(“全局数据”)结构的地方,两者都位于一些现成的RAM (SRAM,
定缓存……)中。在这种情况下,变量全局数据,无论初始化与否(BSS),都是不可用的;只有常量初始化数据可用。在调用board_init_f()之前,GD应该置零。
2、调用board_init_f()。这个函数为从系统RAM (DRAM, DDR…)执行硬件做准备。由于系统RAM可能还不可用,因此board_init_f()必须使用当前GD来存储任何必须传递到以后阶段的数据。这些数据包括重定位目的地、未来堆栈和未来GD位置。
3、设置中间环境,其中堆栈和GD是由board_init_f()在系统RAM中分配的,但BSS和初始化的非const数据仍然不可用。
4a、对于U-Boot本身(不是SPL),调用relocate_code()。这个函数将U-Boot从当前位置重新定位到由board_init_f()计算的重新定位目的地。
4b、对于SPL, board_init_f()只是返回(到crt0)。在SPL中没有代码重定位。
5、设置调用board_init_r()的最终环境。这个环境有BSS(初始化为0)、初始化的非const数据(初始化为预期值)和系统RAM中的堆栈(对于SPL来说,将堆栈和GD移动到RAM中是可选的——请参阅CONFIG_SPL_STACK_R)。GD保留了由board_init_f()设置的值。
6、对于U-Boot本身(而不是SPL),一些cpu在内存方面还有一些工作要做,所以调用c_runtime_cpu_setup。
7、调用board_init_r()。要了解更多信息,请参阅README中的“板初始化流程”。

_main函数里面调用了 board_init_f、relocate_code、relocate_vectors和 board_init_r 这 4个函数,四个函数功能分别是:

  • board_init_f 函数:主要是对部分必要外设进行初始化,如:串口、定时器和打印功能,并对GD成员变量进行内存的分配,防止内核覆盖U-boot。
  • relocate_code:就是将U-boot自身代码拷贝到DRAM中,并进行重定位,继续运行。
  • relocate_vectors:重定位向量表,寄存器中,也就是将新的向量表首地址写入到寄存器 VBAR中,设置向量表偏移。
  • board_init_r:初始化其它外设,如:cache、重定位后的GD成员变量,malloc,控制台、bootstage、74XX芯片, I2C、 FEC、 USB和 QSPI、串口、stdio、电源、nand、emmc、其它cpu核、中断、网络地址等

2.3 U-boot配置

​ U-boot初始化完成后,终端会显示一个等待,在这个等待时间内按下任意键会进入U-boot命令端,功能由函数 run_main_loop 实现,在run_main_loop函数中,包含函数:cli_loop,这个函数用来处理uboot中输入各种命令。cli_loop函数通过调用初始化命令的成员变量,并进行命令解析,通过函数cmd_process对命令进行处理。

​ 无论是否进入U-boot命令端,U-boot要启动内核,必须要调用bootz,下一章将bootz启动Linux内核。

三、bootz启动 Linux内核过程

​ bootz启动Linux内核的过程大致如下:

  • (1)从启动设备把U-boot、操作系统、设备树、虚拟文件系统加载到DDR中;
  • (2)初始化串口等外设;
  • (3)挂载根文件系统;

​ 下面来详细分析bootz启动Linux内核的过程,在bootz中,有一个重要的全局变量images(bootm_headers_t类型),bootm_headers_t结构体如下:

/* * 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;

#if defined(CONFIG_FIT)
	const char	*fit_uname_cfg;	/* configuration node unit name */

	void		*fit_hdr_os;	/* os FIT image header */
	const char	*fit_uname_os;	/* os subimage node unit name */
	int			 fit_noffset_os;	/* os subimage node offset */

	void		*fit_hdr_rd;	/* init ramdisk FIT image header */
	const char	*fit_uname_rd;	/* init ramdisk subimage node unit name */
	int			 fit_noffset_rd;	/* init ramdisk subimage node offset */

	void		*fit_hdr_fdt;	/* FDT blob FIT image header */
	const char	*fit_uname_fdt;	/* FDT blob subimage node unit name */
	int			 fit_noffset_fdt;/* FDT blob subimage node offset */

	void		*fit_hdr_setup;	/* x86 setup FIT image header */
	const char	*fit_uname_setup; /* x86 setup subimage node name */
	int			 fit_noffset_setup;/* x86 setup subimage node offset */
#endif

#ifndef USE_HOSTCC
	image_info_t	os;		/* os image info */
	ulong			ep;		/* entry point of OS */

	ulong		 rd_start, rd_end;/* ramdisk start/end */

	char		*ft_addr;	/* flat dev tree address */
	ulong		 ft_len;		/* length of flat device tree */

	ulong		 initrd_start;
	ulong		 initrd_end;
	ulong		 cmdline_start;
	ulong		 cmdline_end;
	bd_t		*kbd;
#endif

	int		verify;		/* getenv("verify")[0] != 'n' */

#define BOOTM_STATE_START (0x00000001)
#define BOOTM_STATE_FINDOS (0x00000002)
#define BOOTM_STATE_FINDOTHER (0x00000004)
#define BOOTM_STATE_LOADOS (0x00000008)
#define BOOTM_STATE_RAMDISK (0x00000010)
#define BOOTM_STATE_FDT (0x00000020)
#define BOOTM_STATE_OS_CMDLINE (0x00000040)
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100)
#define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */
#define BOOTM_STATE_OS_GO (0x00000400)
	int		state;

#ifdef CONFIG_LMB
	struct lmb	lmb;		/* for memory mgmt */
#endif
} bootm_headers_t;

extern bootm_headers_t images;

第36行,os成员变量是 image_info_t类型,描述系统镜像信息。

typedef struct image_info { 
        
	ulong		start, end;		/* start/end of blob */
	ulong		image_start, image_len; /* start of image within blob, len of image */
	ulong		load;			/* load addr for the image */
	uint8_t		comp, type, os;		/* compression, type of image, os type */
	uint8_t		arch;			/* CPU architecture */
} image_info_t;

booz命令通过do_bootz函数实现,函数中,执行了三个函数:bootz_start,bootm_disable_interrupts,do_bootm_states

int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{ 
        
	int ret;
	/* Consume 'bootz' */
	argc--; argv++;
	if (bootz_start(cmdtp, flag, argc, argv, &images))
		return 1;
	/* * We are doing the BOOTM_STATE_LOADOS state ourselves, so must * disable interrupts ourselves */
	bootm_disable_interrupts();
	images.os.os = IH_OS_LINUX;
	ret = do_bootm_states(cmdtp, flag, argc, argv,
			      BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
			      BOOTM_STATE_OS_GO,
			      &images, 1);
	return ret;
}

​ 函数先调用bootz_start进行初始化,然后调用bootm_disable_interrupts关闭中断,再设置 images.os.os为 IH_OS_LINUX,设置系统镜像为 Linux,最后调用函数 do_bootm_states来执行不同的 BOOT阶段。do_bootm_states函数略复杂,先放到后面。

​ 先看下bootz_start函数代码:

static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
			char * const argv[], bootm_headers_t *images)
{ 
        
	int ret;
	ulong zi_start, zi_end;

	ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
			      images, 1);

	/* Setup Linux kernel zImage entry point */
	if (!argc) { 
        
		images->ep = load_addr;
		debug("* kernel: default image load address = 0x%08lx\n",
				load_addr);
	} else { 
        
		images->ep = simple_strtoul(argv[0], NULL, 16);
		debug("* kernel: cmdline image address = 0x%08lx\n",
			images->ep);
	}

	ret = bootz_setup(images->ep, &zi_start, &zi_end);
	if (ret != 0)
		return 1;

	lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);

	/* * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not * have a header that provide this informaiton. */
	if (bootm_find_images(flag, argc, argv))
		return 1;

#ifdef CONFIG_SECURE_BOOT
	extern uint32_t authenticate_image(
			uint32_t ddr_start, uint32_t image_size);
	if (authenticate_image(images->ep, zi_end - zi_start) == 0) { 
        
		printf("Authenticate zImage Fail, Please check\n");
		return 1;
	}
#endif
	return 0;
}

​ bootz_start函数先调用了do_boom_states函数,然后通过images->ep = load_addr获取镜像系统的入口地址,接着调用 bootz_setup函数,判断当前的系统镜像文件是否为 Linux的镜像文件,并打印出镜像相关信息,最后调用函bootm_find_images查找设备树 (dtb)文件。

​ 前面两次提到do_bootm_states函数,do_bootm_states函数的执行会很具states执行不同的代码,在do_bootz函数中,用到的状态有:BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO (do_bootz中的调用可知),bootz_start中用到了:BOOTM_STATE_START,全部代码如下,但我们只需要看这四个状态的代码就可以了:

int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
		    int states, bootm_headers_t *images, int boot_progress)
{ 
        
	boot_os_fn *boot_fn;
	ulong iflag = 0;
	int ret = 0, need_boot_fn;

	images->state |= states;

	/* * Work through the states and see how far we get. We stop on * any error. */
	if (states & BOOTM_STATE_START)
		ret = bootm_start(cmdtp, flag, argc, argv);

	if (!ret && (states & BOOTM_STATE_FINDOS))
		ret = bootm_find_os(cmdtp, flag, argc, argv);

	if (!ret && (states & BOOTM_STATE_FINDOTHER)) { 
        
		ret = bootm_find_other(cmdtp, flag, argc, argv);
		argc = 0;	/* consume the args */
	}

	/* Load the OS */
	if (!ret && (states & BOOTM_STATE_LOADOS)) { 
        
		ulong load_end;

		iflag = bootm_disable_interrupts();
		ret = bootm_load_os(images, &load_end, 0);
		if (ret == 0)
			lmb_reserve(&images->lmb, images->os.load,
				    (load_end - images->os.load));
		else if (ret && ret != BOOTM_ERR_OVERLAP)
			goto err;
		else if (ret == BOOTM_ERR_OVERLAP)
			ret = 0;
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
		if (images->os.os == IH_OS_LINUX)
			fixup_silent_linux();
#endif
	}

	/* Relocate the ramdisk */
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
	if (!ret && (states & BOOTM_STATE_RAMDISK)) { 
        
		ulong rd_len = images->rd_end - images->rd_start;

		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
#if defined(CONFIG_OF_LIBFDT) && defined(CONFIG_LMB)
	if (!ret && (states & BOOTM_STATE_FDT)) { 
        
		boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
		ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
					&images->ft_len);
	}
#endif

	/* From now on, we need the OS boot function */
	if (ret)
		return ret;
	boot_fn = bootm_os_get_boot_func(images->os.os);
	need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
			BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
			BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
	if (boot_fn == NULL && need_boot_fn) { 
        
		if (iflag)
			enable_interrupts();
		printf("ERROR: booting os '%s' (%d) is not supported\n",
		       genimg_get_os_name(images->os.os), images->os.os);
		bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
		return 1;
	}

	/* Call various other states that are not generally used */
	if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
		ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
	if (!ret && (states & BOOTM_STATE_OS_BD_T))
		ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
	if (!ret && (states & BOOTM_STATE_OS_PREP))
		ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);

#ifdef CONFIG_TRACE
	/* Pretend to run the OS, then run a user command */
	if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) { 
        
		char *cmd_list = getenv("fakegocmd");

		ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
				images, boot_fn);
		if (!ret && cmd_list)
			ret = run_command_list(cmd_list, -1, flag);
	}
#endif

	/* Check for unsupported subcommand. */
	if (ret) { 
        
		puts("subcommand not supported\n");
		return ret;
	}

	/* Now run the OS! We hope this doesn't return */
	if (!ret && (states & BOOTM_STATE_OS_GO))
		ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
				images, boot_fn);

	/* Deal with any fallout */
err:
	if (iflag)
		enable_interrupts();

	if (ret == BOOTM_ERR_UNIMPLEMENTED)
		bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
	 

相关文章