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启动流程的理解需要一定的汇编基础。下面是做的一个思维导图,顺序是从左到右,从上往下。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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. 设置参数并跳转到操作系统;
详细启动流程会在下面章节进行介绍。
了解一个系统的启动流程,就要先找到它的入口函数,这样才能从源头开始分析一个系统的启动流程,U-boot的启动流程要从它的编译脚本开始找入口函数。入口函数主要完成的工作是:
移植之前,要确定移植的源开发板,找到它的编译脚本,但是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异常表代码,主要用来:
在连接脚本的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)
U-boot初始化部分主要完成以下工作:
内存初始化完成后,再回到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个函数,四个函数功能分别是:
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内核的过程,在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,这样就会减少很多的工作量。本人用的是NXP的imx6ull芯片,核心板是正点原子的,正点原子的开发板参照的是NXP的I.MX6ULL EVK的硬件电路设计的,本文只做一个简要的总结,详细具体内容可以参考正点原子的驱动手册U-boot移植部分。
U-boot的移植内容不多,在不做修剪的情况下,仅需要移植三部分:
如果我们的电路有大幅度改变,需要修改驱动文件,这个在此不做讨论,不同的驱动修改不一样,根据改动修改。对于U-boot的裁剪,可以在图形界面中选择,需要什么选什么。下面是具体的移植操作。
1、U-boot移植默认已经获取了厂家的源码,首先要移植的是U-boot的配置文件,配置文件再U-boot根目录的configs中,里面有很多芯片对应的配置文件,参照的是NXP的I.MX6ULL EVK开发板,所以搜索一下找到这些,具体参照哪个就复制哪个,然后命名为自己开发板的配置文件。
比如我命名为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