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

一、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       0x0000000087800000      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);
	else if (ret == BOOTM_ERR_RESET)
		do_reset(cmdtp, flag, argc, argv);

	return ret;
}

​ 如果states = BOOTM_STATE_START 那么会执行 bootm_start 函数,这个函数是初始化配置,函数代码:

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");

	boot_start_lmb(&images);

	bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
	images.state = BOOTM_STATE_START;

	return 0;
}

​ 接着do_bootm_states函数(第68行)会通过bootm_os_get_boot_func函数查找系统启动函数,Linux系统启动函数为 do_bootm_linux 。
​ 如果states = BOOTM_STATE_OS_PREP 会调用函数 do_bootm_linux ,再调用boot_prep_linux函数处理环境变量第108行,调用函数 boot_selected_os启动 Linux内核。
​ boot_selected_os函数里面有着Linux内核启动的具体流程:通过boot_fn调用do_bootm_linux,再改变标志位,调用boot_jump_linux函数:如果内核没有设备树的话,会通过变量 machid保存机器 ID,与Linux内核匹配,查看内核是否支持。之后获取kernel入口函数kernel_entry,并调用announce_and_cleanup来打印一些信息并做一些清理工作。boot_jump_linux代码:

/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64
	void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,
			void *res2);
	int fake = (flag & BOOTM_STATE_OS_FAKE_GO);

	kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
				void *res2))images->ep;

	debug("## Transferring control to Linux (at address %lx)...\n",
		(ulong) kernel_entry);
	bootstage_mark(BOOTSTAGE_ID_RUN_OS);

	announce_and_cleanup(fake);

	if (!fake) {
		do_nonsec_virt_switch();
		kernel_entry(images->ft_addr, NULL, NULL, NULL);
	}
#else
	unsigned long machid = gd->bd->bi_arch_number;
	char *s;
	void (*kernel_entry)(int zero, int arch, uint params);
	unsigned long r2;
	int fake = (flag & BOOTM_STATE_OS_FAKE_GO);

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

	s = getenv("machid");
	if (s) {
		if (strict_strtoul(s, 16, &machid) < 0) {
			debug("strict_strtoul failed!\n");
			return;
		}
		printf("Using machid 0x%lx from environment\n", machid);
	}

	debug("## Transferring control to Linux (at address %08lx)" \
		"...\n", (ulong) kernel_entry);
	bootstage_mark(BOOTSTAGE_ID_RUN_OS);
	announce_and_cleanup(fake);

	if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
		r2 = (unsigned long)images->ft_addr;
	else
		r2 = gd->bd->bi_boot_params;

	if (!fake) {
#ifdef CONFIG_ARMV7_NONSEC
		if (armv7_boot_nonsec()) {
			armv7_init_nonsec();
			secure_ram_addr(_do_nonsec_entry)(kernel_entry,
							  0, machid, r2);
		} else
#endif
			kernel_entry(0, machid, r2);
	}
#endif
}

三、U-boot移植

​ 对于U-boot的移植,首先并不建议从U-boot官网直接下载U-boot从零开始移植,因为这个工作是非常复杂麻烦的,短时间内不可能完成,对移植人员也有很高的要求,所以最好是从半导体厂商,就是买的芯片的这家厂商的官网去找对应的U-boot,这样就会减少很多的工作量。本人用的是NXP的imx6ull芯片,核心板是正点原子的,正点原子的开发板参照的是NXP的I.MX6ULL EVK的硬件电路设计的,本文只做一个简要的总结,详细具体内容可以参考正点原子的驱动手册U-boot移植部分。

​ U-boot的移植内容不多,在不做修剪的情况下,仅需要移植三部分:

  • 配置文件;
  • 开发板头文件;
  • 板级文件;

​ 如果我们的电路有大幅度改变,需要修改驱动文件,这个在此不做讨论,不同的驱动修改不一样,根据改动修改。对于U-boot的裁剪,可以在图形界面中选择,需要什么选什么。下面是具体的移植操作。

1、U-boot移植默认已经获取了厂家的源码,首先要移植的是U-boot的配置文件,配置文件再U-boot根目录的configs中,里面有很多芯片对应的配置文件,参照的是NXP的I.MX6ULL EVK开发板,所以搜索一下找到这些,具体参照哪个就复制哪个,然后命名为自己开发板的配置文件。2021年Linux技术总结(一):U-boot_第1张图片
​ 比如我命名为mx6ull_Xport_emmc_defconfig,然后打开,修改一下配置的信息,mx6ull_14x14_evk_emmc_defconfig内容如下:

CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ullevk/imximage.cfg,MX6ULL_EVK_EMMC_REWORK"
CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_TARGET_MX6ULL_14X14_EVK=y
CONFIG_CMD_GPIO=y

​ 需要修改的是含有mx6ull evk这些字样的信息,其实移植第一大步就是先改个开发板的名字,将它们改成:

CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_Xport_emmc/imximage.cfg,MX6ULL_EVK_EMMC_REWORK"
CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_TARGET_MX6ULL_XPORT_EMMC=y
CONFIG_CMD_GPIO=y

2、配置信息移植修改完后,找到开发板的头文件,它们一般再include/configs里面,搜索mx6ullevk,这里就一个,因为NXP的9x9,14x14其实都差不多,将这个复制一下,改成我们开发板的名字,并打开文件,把#ifndef #define改了(别告诉我不知道为什么要改头文件,狗头)。
3、头文件改完就是板级文件了,这个再board里面,因为U-boot里面有很多开发板的配置,再进入freescale,freescale就是nxp,它们在2015年合并,可怜的飞思卡尔连名字都没了,搜索mx6ullevk,这个文件夹里面的就是对整个开发板的配置,复制真个文件夹为我们的板子,进入,修改一下.c文件,名字也改成我们的。
3.1、下面,修改Makefile文件,里面的编译对象改成刚刚.c文件的名字:

obj-y  := mx6ullevk.o  // 这个改成我们开发板的.c的名字

extra-$(CONFIG_USE_PLUGIN) :=  plugin.bin
$(obj)/plugin.bin: $(obj)/plugin.o
	$(OBJCOPY) -O binary --gap-fill 0xff $< $@

3.2、修改imximage.cfg,这里面有一个

PLUGIN	board/freescale/mx6ullevk/plugin.bin 0x00907000

​ 这个路径要改成这个板级文件夹的路径,还有一个Kconfig文件:

if TARGET_MX6ULL_14X14_EVK || TARGET_MX6ULL_9X9_EVK //这个判断条件改了,换成我们的,这个名字要和第一个移植的CONFIG后面的一样

config SYS_BOARD
	default "mx6ullevk"	//改名字

config SYS_VENDOR
	default "freescale"
---------------------------------
config SYS_SOC			// 新加的,不加这个会出错,编译不过,忘了什么原因
	default mx6
---------------------------------
config SYS_CONFIG_NAME
	default "mx6ullevk"	//改名字

endif

3.3、MAINTAINERS文件:

MX6ULLEVK BOARD
M:	Peng Fan <peng.fan@nxp.com>
S:	Maintained
F:	board/freescale/mx6ullevk/	//改名字
F:	include/configs/mx6ullevk.h	//改名字
F:	configs/mx6ull_14x14_evk_defconfig	//删
F:	configs/mx6ull_9x9_evk_defconfig	//删

​ 3.4、把它加到U-Boot图形界面配置文件里面,在 arch/arm/cpu/armv7/mx6/Kconfig文件中添加上下面格式的内容:

config TARGET_MX6UL_9X9_EVK 	//改名字
	bool "mx6ul_9x9_evk" 		//改名字
	select MX6UL				//MX6ULL
	select DM
	select DM_THERMAL
	select SUPPORT_SPL

source "board/freescale/mx6ul_14x14_evk/Kconfig"	//改路径,板级文件路径

​ 4、最后,附送一个移植NXP的mx6ullevk的脚本,只需要改一下名字就可以了:

#!/bin/bash

board=Xport_Alientek //把这两个改一下,对应一下
BOARD=XPORT_ALIENTEK //把这两个改一下,对应一下

# Adding a compilation script
touch ${board}_building.sh
chmod 777 ${board}_building.sh
echo '#!/bin/bash' > ${board}_building.sh
echo '' >> ${board}_building.sh
echo "make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean" >> ${board}_building.sh
echo "make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_$board\_emmc_defconfig" >> ${board}_building.sh
echo "make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8" >> ${board}_building.sh

# Add the development board default configuration file
cd configs 
cp mx6ull_14x14_evk_emmc_defconfig mx6ull_$board\_emmc_defconfig

echo CONFIG_SYS_EXTRA_OPTIONS='"'IMX_CONFIG=board/freescale/mx6ull_$board\_emmc/imximage.cfg,MX6ULL_EVK_EMMC_REWORK'"' > mx6ull_$board\_emmc_defconfig
echo CONFIG_ARM=y >> mx6ull_$board\_emmc_defconfig
echo CONFIG_ARCH_MX6=y >> mx6ull_$board\_emmc_defconfig
echo CONFIG_TARGET_MX6ULL_$BOARD\_EMMC=y >> mx6ull_$board\_emmc_defconfig
echo CONFIG_CMD_GPIO=y >> mx6ull_$board\_emmc_defconfig

# Add the development board header file
cd ../include/configs/
cp mx6ullevk.h mx6ull_$board\_emmc.h
CON1="#ifndef __MX6ULL_${BOARD}_EMMC_CONFIG_H"
CON2="#define __MX6ULL_${BOARD}_EMMC_CONFIG_H"
sed -i "8c${CON1}" mx6ull_$board\_emmc.h
sed -i "9c${CON2}" mx6ull_$board\_emmc.h

# Add development board-level folders
cd ../../board/freescale/
rm -rf mx6ull_$board\_emmc
cp mx6ullevk/ -r mx6ull_$board\_emmc
cd mx6ull_$board\_emmc
mv mx6ullevk.c mx6ull_$board\_emmc.c

# Modify the development board name
declare -i nline
getline()
{
	cat -n mx6ull_$board\_emmc.c|grep "checkboard(void)"|awk '{print $1}'
}
getlinenum()
{
	awk "BEGIN{a=`getline`;b="1";c=(a+b);print c}";
}
nline=`getlinenum`+4
sed -i "${nline}c\		puts("'"'"Board: MX6ULL ${BOARD} EMMC "'\\n"'");" mx6ull_$board\_emmc.c

CON3="obj-y  := mx6ull_${board}_emmc.o"
sed -i "6c${CON3}" Makefile
CON4="PLUGIN	board/freescale/mx6ull_${board}_emmc/plugin.bin 0x00907000"
sed -i "34c${CON4}" imximage.cfg

 # Alter Kconfig
echo if TARGET_MX6ULL_$BOARD\_EMMC > Kconfig
echo "" >> Kconfig
echo config SYS_BOARD >> Kconfig
echo "	"default "mx6ull_${board}_emmc" >> Kconfig
echo "" >> Kconfig
echo config SYS_VENDOR >> Kconfig
echo "	"default "freescale" >> Kconfig
echo "" >> Kconfig
echo config SYS_SOC >> Kconfig
echo "	"default "mx6" >> Kconfig
echo "" >> Kconfig
echo config SYS_CONFIG_NAME >> Kconfig
echo "	"default "mx6ull_${board}_emmc" >> Kconfig
echo "" >> Kconfig
echo endif >> Kconfig

echo "MX6ULL_${BOARD}_EMMC BOARD" > MAINTAINERS
echo "M:	Peng Fan " >> MAINTAINERS
echo "S:	Maintained" >> MAINTAINERS
echo "F:	board/freescale/mx6ull_${board}_emmc/" >> MAINTAINERS
echo "F:	include/configs/mx6ull_${board}_emmc.h" >> MAINTAINERS

# Modifying the compiled file
cd ../../../arch/arm/cpu/armv7/mx6/
getline()
{
	cat -n Kconfig|grep "config TARGET_MX6ULL_${BOARD}_EMMC"|awk '{print $1}'
}
if [ `getline` > 0 ]
then
	echo The development board : $board already exists
else
	declare -i nline
	getline()
	{
		cat -n Kconfig|grep "TARGET_MX6UL_9X9_EVK"|awk '{print $1}'
	}
	getlinenum()
	{
		awk "BEGIN{a=`getline`;b="1";c=(a+b);print c}";
	}
	nline=`getlinenum`+5
	sed -i "${nline}a\config TARGET_MX6ULL_${BOARD}_EMMC\n	bool "'"'"Support mx6ull_${board}_emmc"'"'"\n	select MX6ULL\n	select DM\n	select DM_THERMAL\n" Kconfig
fi

getline()
{
	cat -n Kconfig|grep "board/freescale/mx6ull_${board}_emmc/Kconfig"|awk '{print $1}'
}
if [ `getline` > 0 ]
then
	echo The development board : $board already exists
else
	declare -i nline
	getline()
	{
		cat -n Kconfig|grep "board/freescale/mx6sxscm/Kconfig"|awk '{print $1}'
	}
	getlinenum()
	{
		awk "BEGIN{a=`getline`;b="1";c=(a+b);print c}";
	}
	nline=`getlinenum`-1
	sed -i "${nline}a\source "'"'"board/freescale/mx6ull_${board}_emmc/Kconfig"'"' Kconfig
	echo The new U-boot : $board is added
fi

四、其它修改

​ 对U-boot的配置设置主要就是在开发板的.c和.h文件,举个栗子,对U-boot打印串口的修改,注:修改前记得看一下有没有被复用为别的:

1、include/configs/mx6_common.h	//开发板系列
#define CONFIG_CONS_INDEX	      	1 			--------> 	#define CONFIG_CONS_INDEX	      	'n'
2、include/configs/mx6ullevk.h		//开发板头文件
 define CONFIG_MXC_UART_BASE        UART3_BASE 	-------->  	define CONFIG_MXC_UART_BASE 		UART'n'_BASE
CONFIG_EXTRA_ENV_SETTINGS 内容中:ttymx0 改为 ttymx'n-1'
3、board/freescale/mx6ullevk		//开发板源文件
static iomux_v3_cfg_t const uart1_pads[]函数中的
MX6_PAD_UART1_TX_DATA__UART1_DCE_TX
MX6_PAD_UART1_RX_DATA__UART1_DCE_RX
改为
对应串口:
MX6_PAD_UART'n'_TX_DATA__UART'n'_DCE_TX
MX6_PAD_UART'n'_RX_DATA__UART'n'_DCE_RX

你可能感兴趣的:(Linux,学习,linux,u-boot)