一、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_<os>() * 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);