uboot启动流程

1 uboot链接脚本u-boot.lds

  参考文档
  u-boot.lds在uboot源码根目录下,但是实际上uboot源码根目录原来是没有u-boot.lds的,是uboot Makefile从其他位置拷贝、修改生成的。
  uboot Makefile

ifndef LDSCRIPT
        ifeq ($(wildcard $(LDSCRIPT)),)
               LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds
        endif
        ifeq ($(wildcard $(LDSCRIPT)),)
               LDSCRIPT := $(srctree)/$(CPUDIR)/u-boot.lds
        endif
        ifeq ($(wildcard $(LDSCRIPT)),)
               LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot.lds //实际路径 arch/arm/cpu/u-boot.lds
        endif
endif

  uboot根目录下也是实际使用的u-boot.lds

/* 指定可执行文件是32位、elf格式,arm 小端序 */
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/* 指定arm架构 */
OUTPUT_ARCH(arm)
/* 指定起始代码段为_stext,_stext在./arch/arm/cpu/slsiap/s5p6818/start.S */
ENTRY(_stext)
SECTIONS
{/* 全局入口点,这个位置(0x0)通常是存储uboot flash(常用emmc)的0x0地址 */
 . = 0x00000000;
 . = ALIGN(4); /* 4字节对齐 */
 .text : /* 代码段开始 */
 {/* 将uboot拷贝到内存中,先拷贝start.o,然后拷贝其他文件,也就是说在ram中start.o地址最前,也就是说start.o在ram中装载地址、执行地址都0x0 */
  *(.__image_copy_start)
  /* 在arch/arm/cpu/u-boot.lds中,下面一行是CPUDIR/start.o (.text*),CPUDIR也就是arch/arm/cpu/slsiap/s5p6818/目录 */
  arch/arm/cpu/slsiap/s5p6818/start.o (.text*)
  *(.text*) /* 除了start.o外其他输出文件的代码段 */
 }
 /* 代码段,内存可能并不是4字节对齐,这里需要再4字节对齐一次 */
 . = ALIGN(4);
 /* rodata段开始,保存只读数据,由所有输出文件的rodata段组成 */
 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
 . = ALIGN(4); /* 同样4字节对齐 */
 /* data段开始 */
 .data : {
  *(.data*)
 }
 . = ALIGN(4);
 . = .;
 . = ALIGN(4);
 /*存放u-boot自有的一些function,例如u-boot command等*/
 .u_boot_list : {
  KEEP(*(SORT(.u_boot_list*)));
 }
 . = ALIGN(4);
 .image_copy_end :
 {
 /* u-boot需要自拷贝的内容结束,总结一下,包括代码段,数据段,以及u_boot_list */
  *(.__image_copy_end)
 }
 .rel_dyn_start :
 {
 /*在老的uboot中,如果我们想要uboot启动后把自己拷贝到内存中的某个地方,只要把要拷贝的地址写给TEXT_BASE即可,然后boot启动后就会把自己拷贝到TEXT_BASE内的地址处运行,在拷贝之前的代码都是相对的,不能出现绝对的跳转,否则会跑飞。在新版的uboot里(2013.07),TEXT_BASE的含义改变了。它表示用户要把这段代码加载到哪里,通常是通过串口等工具。然后搬移的时候由uboot自己计算一个地址来进行搬移。新版的uboot采用了动态链接技术,在lds文件中有__rel_dyn_start和__rel_dyn_end,这两个符号之间的区域存放着动态链接符号,只要给这里面的符号加上一定的偏移,拷贝到内存中代码的后面相应的位置处,就可以在绝对跳转中找到正确的函数。*/
  *(.__rel_dyn_start)
 }
 .rel.dyn : {
 /* 动态链接符存放在的段 */
  *(.rel*)
 }
 .rel_dyn_end :
 {
  *(.__rel_dyn_end)
 }
 .end :
 {
  *(.__end)
 }
 _image_binary_end = .;
 . = ALIGN(4096);
 .mmutable : {
  *(.mmutable)
 }
  /* bss段 */
 .bss_start (OVERLAY) : {
  KEEP(*(.__bss_start));
  __bss_base = .;  /* 把__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.*) }
}

  start.o中会有一个u-boot自拷贝及重定位过程,start.o执行到最后时,整个u-boot已经被复制到了内存的TEXT_BASE(0x42C00000)位置。TEXT_BASE可以从uboot源码根目录下Makefile中找到,Makefile实际用的是CONFIG_SYS_TEXT_BASE,这个宏定义在include/configs/s5p6818_drone.h中

#define CONFIG_SYS_TEXT_BASE            0x42C00000
#define CONFIG_SYS_INIT_SP_ADDR         CONFIG_SYS_TEXT_BASE    /* init and run stack pointer */

  所以重定位后uboot(应该包含start.o,已执行完)的执行地址变更为0x42c00000

2 start.S

(u-boot\arch\arm\cpu\slsiap\s5p6818)

  异常向量表及uboot首地址

    .globl  _stext /* 整个uboot首地址_stext,接下来就是reset、异常向量表 */
_stext:
    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

  重定位是在reset中完成的

#ifdef CONFIG_RELOC_TO_TEXT_BASE
relocate_to_text:
    /*
     * relocate u-boot code on memory to text base
     * for nexell arm core (add by jhkim)
     */
    adr r0, _stext              /*r0 <- current position of code,uboot首地址,从start.o _stext开始*/
    ldr r1, TEXT_BASE           /* test if we run from flash or RAM,重定位的地址 */
    cmp r0, r1                  /* don't reloc during debug,两个地址相同,重定位已经完成,跳转到clear_bss */
    beq clear_bss               

    ldr r2, _bss_start_ofs /* 变量定义在start.S等于__bss_start - _stext,也就是__bss_start相对于_stext的偏移 */
    add r2, r0, r2              /* r2 <- source end address 现在r2就是__bss_start的地址         */

copy_loop_text:
    ldmia   r0!, {r3-r10}       /* copy from source address [r0]    */
    stmia   r1!, {r3-r10}       /* copy to   target address [r1]    */
    cmp r0, r2                  /* until source end addreee [r2]    */
    ble copy_loop_text /* 循环拷贝,将uboot中__bss_start之前的内容拷贝到TEXT_BASE地址开始的位置 */

    ldr r1, TEXT_BASE           /* restart at text base */
    mov pc, r1 /* 从TEXT_BASE开始执行,start.S会被再执行一次,重定位不再执行,跳转到clear_bss */

  clear_bss 完成比较多的工作

clear_bss:
#ifdef CONFIG_MMU_ENABLE
    bl  mmu_turn_on /* 1 开mmu */
#endif
    ldr r0, _bss_start_ofs
    ldr r1, _bss_end_ofs
    ldr r4, TEXT_BASE           /* text addr */
    add r0, r0, r4
    add r1, r1, r4
    mov r2, #0x00000000         /* clear */

clbss_l:str r2, [r0]            /* clear loop... 2循环清bss */
    add r0, r0, #4
    cmp r0, r1
    bne clbss_l

    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
    bic sp, sp, #7                  /* 8-byte alignment for ABI compliance */
    sub sp, #GD_SIZE                /* allocate one GD above SP */
    bic sp, sp, #7                  /* 8-byte alignment for ABI compliance */
    mov r9, sp                      /* GD is above SP */
    mov r0, #0
    bl  board_init_f /* 3 设置好需要传递的参数后,调用board_init_f(在common/board_f.c中)  */

    mov sp, r9                      /* SP is GD's base address */
    bic sp, sp, #7                  /* 8-byte alignment for ABI compliance */
    sub sp, #GENERATED_BD_INFO_SIZE /* allocate one BD above SP */
    bic sp, sp, #7                  /* 8-byte alignment for ABI compliance */

    mov r0, r9                      /* gd_t *gd */
    ldr r1, TEXT_BASE               /* ulong text */
    mov r2, sp                      /* ulong sp */
    bl  gdt_reset /* 4 调用gdt_reset */

    /* call board_init_r(gd_t *id, ulong dest_addr) */
    mov r0, r9                          /* gd_t */
    ldr r1,  =(CONFIG_SYS_MALLOC_END)   /* dest_addr for malloc heap end */
    /* call board_init_r 5调用board_init_r(board_r.c) */
    ldr pc, =board_init_r               /* this is auto-relocated! */

  那么uboot中C代码被调用的三个函数就是board_init_f、gdt_reset、board_init_r最后转入到board_init_r代码,彻底进入C代码,汇编完成。

3 common/board_f.c

  board_init_f 函数中最重要的就是init_sequence_f,board_init_f 实际的工作就是调用init_sequence_f数组中的函数。将init_sequence_f中实际未用到的部分去掉

static init_fnc_t init_sequence_f[] = {//重点函数有几个
    setup_mon_len,
    setup_fdt,
    trace_early_init,
    arch_cpu_init,      /* 1 cpu 初始化 cpu.c  kernel\arch\arm\mach-s5p6818 */
    mark_bootstage,
#ifdef CONFIG_OF_CONTROL
    fdtdec_check_fdt,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
    board_early_init_f, /* 2 gpio pmu rtc 初始化 board.c   kernel\arch\arm\plat-s5p6818\drone */
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS)
    timer_init,     /* 3 timer 初始化 */
#endif
    env_init,       /* 4 环境变量初始化 */
    init_baud_rate,     /* 5 串口、波特率初始化,串口初始化完成后就可以打印调试信息了 */
    serial_init,        /* serial communications setup */
    console_init_f,     /* stage 1 init of console */

#ifdef CONFIG_OF_CONTROL
    fdtdec_prepare_fdt,
#endif
    display_options,    /* say that we are here */
    display_text_info,  /* show debugging info if required */

    print_cpuinfo,      /* 6 打印cpu信息 display cpu info (and speed) */

#if defined(CONFIG_DISPLAY_BOARDINFO)   /*未定义*/
    checkboard,     /* display board info */
#endif
    INIT_FUNC_WATCHDOG_INIT

    INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
    init_func_i2c, /* 7 i2c初始化 */
#endif
#if defined(CONFIG_HARD_SPI)    /*未定义*/
    init_func_spi,
#endif
    announce_dram_init,
    /* TODO: unify all these dram functions? */
#ifdef CONFIG_ARM /* 8 内存初始化,这里仅获取内存大小 */
    dram_init,      /* configure available RAM banks */
#endif
    INIT_FUNC_WATCHDOG_RESET
    INIT_FUNC_WATCHDOG_RESET
    INIT_FUNC_WATCHDOG_RESET
    /*
     * Now that we have DRAM mapped and working, we can
     * relocate the code and continue running from DRAM.
     *
     * Reserve memory at end of RAM for (top down in that order):
     *  - area that won't get touched by U-Boot and Linux (optional)
     *  - kernel log buffer
     *  - protected RAM
     *  - LCD framebuffer
     *  - monitor code
     *  - board info struct
     */
    setup_dest_addr,

    reserve_round_4k,
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
        defined(CONFIG_ARM)
    reserve_mmu,
#endif
#ifdef CONFIG_LCD /*未定义*/
    reserve_lcd,
#endif
    reserve_trace,

    reserve_uboot,
#ifndef CONFIG_SPL_BUILD
    reserve_malloc,
    reserve_board,
#endif
    setup_machine,
    reserve_global_data,
    reserve_fdt,
    reserve_stacks,
    setup_dram_config, /* 8 配置、显示内存参数 */
    show_dram_config,
    display_new_sp,
    INIT_FUNC_WATCHDOG_RESET
    reloc_fdt,
    setup_reloc,
    NULL,
};

3.1 env_init

  使用的是emmc,所以调用的env_init函数是在env_emmc.c(u-boot/common)

int env_init(void)
{
    /* use default */
    gd->env_addr    = (ulong)&default_environment[0];
    gd->env_valid   = 1;
    return 0;
}

  default_environment数组是在include/env_default.h定义的,default_environment数组中保存的是一个个宏定义的环境变量,需要使用的环境变量通常会在板子的头文件中定义,这里的头文件是s5p6818_drone.h u-boot\include\configs。
  default_environment数组中用到一个CONFIG_BOOTDELAY宏,在s5p6818_drone.h中定义为
#define CONFIG_BOOTDELAY 0

4 gdt_reset

  gdt_reset函数在cpu.c u-boot\arch\arm\cpu\slsiap\s5p6818,主要是对uboot全局变量gd及其成员bd初始化。

void gdt_reset(gd_t *gd, ulong text, ulong sp)
{
    ulong text_start, text_end, heap_end;
    ulong bd, pc;

    /* for smp cores */
    global_descriptor = gd; /* 全局变量gd */

    /* reconfig stack info */
    gd->relocaddr = text;
    gd->start_addr_sp = sp;
    gd->reloc_off = 0;

    /* copy bd info  */
    bd = (ulong)gd - sizeof(bd_t); /* bd board保存板子的信息 */
    memcpy((void *)bd, (void *)gd->bd, sizeof(bd_t));

    /* reset gd->bd */
    gd->bd = (bd_t *)bd;

    /* prevent dataabort, when access enva_addr + data (0x04) */
    gd->env_addr = (ulong)default_environment;

    /* get cpu info */
    text_start = (ulong)(gd->relocaddr);
    text_end = (ulong)(gd->relocaddr + _bss_end_ofs);
    heap_end = CONFIG_SYS_MALLOC_END;

#if defined(CONFIG_SYS_GENERIC_BOARD)
    /* refer initr_malloc (common/board_r.c) */
    gd->relocaddr = heap_end;
#endif
    flush_dcache_all();
#if defined(CONFIG_DISPLAY_CPUINFO)
    asm("mov %0, pc":"=r" (pc));
    asm("mov %0, sp":"=r" (sp));

    printf("Heap = 0x%08lx~0x%08lx\n", heap_end-TOTAL_MALLOC_LEN, heap_end);
    printf("Code = 0x%08lx~0x%08lx\n", text_start, text_end);
    printf("GLD  = 0x%08lx\n", (ulong)gd);
    printf("GLBD = 0x%08lx\n", (ulong)gd->bd);
    printf("SP   = 0x%08lx,0x%08lx(CURR)\n", gd->start_addr_sp, sp);

    printf("TAGS = 0x%08lx \n", gd->bd->bi_boot_params);
    #ifdef CONFIG_MMU_ENABLE
    ulong page_tlb =  (text_end & 0xffff0000) + 0x10000;
    printf("PAGE = 0x%08lx~0x%08lx\n", page_tlb, page_tlb + 0xc000 );
    #endif
    #ifdef CONFIG_SMP
    printf("SMPSP= 0x%08x (-0x%08x)\n", CONFIG_SYS_SMP_SP_ADDR, CONFIG_SYS_SMP_SP_SIZE*(NR_CPUS-1));
    #endif
    printf("MACH = [%ld]   \n", gd->bd->bi_arch_number);
    printf("VER  = %u      \n", nxp_cpu_version());
    printf("BOARD= [%s]    \n", CONFIG_SYS_BOARD);
#endif
}

5 board_init_r

  board_init_r在board_r.c u-boot\common中,这个函数的实际完成init_sequence_r数组中函数的调用、执行,数组中有部分重要函数需要特别注意

init_fnc_t init_sequence_r[] = {
    initr_trace,
    initr_reloc,
    /* TODO: could x86/PPC have this also perhaps? */
#ifdef CONFIG_ARM
    initr_caches,
    board_init, /* Setup chipselects */
#endif
    /*
     * TODO: printing of the clock inforamtion of the board is now
     * implemented as part of bdinfo command. Currently only support for
     * davinci SOC's is added. Remove this check once all the board
     * implement this.
     */
#ifdef CONFIG_CLOCKS
    set_cpu_clk_info, /* Setup clock information */
#endif
    initr_reloc_global_data,
    initr_serial,
    initr_announce,
    INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_PPC
    INIT_FUNC_WATCHDOG_RESET
    INIT_FUNC_WATCHDOG_RESET
    initr_barrier,
    initr_malloc,
    bootstage_relocate,
    power_init_board,
    INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_GENERIC_MMC
    initr_mmc, /* 1 mmc初始化 */
#endif
    initr_env, /* 2 环境变量初始化 */
    INIT_FUNC_WATCHDOG_RESET
    initr_secondary_cpu,
    INIT_FUNC_WATCHDOG_RESET
    stdio_init, /* 3 部分平台uboot显示需要走stdio_init, 但是6818显示不走stdio_init */
    initr_jumptable,
    console_init_r,     /* fully init console as a device */
    INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_CMD_KGDB
    initr_kgdb,
#endif
    interrupt_init,
#if defined(CONFIG_ARM) || defined(CONFIG_x86)
    initr_enable_interrupts,
#endif
    /* PPC has a udelay(20) here dating from 2002. Why? */
#ifdef CONFIG_CMD_NET
    initr_ethaddr,
#endif
#ifdef CONFIG_BOARD_LATE_INIT
    board_late_init, /* 4 6818 fastboot升级走board_late_init,并且显示部分从这里开始 */
#endif
#ifdef CONFIG_CMD_NET
    INIT_FUNC_WATCHDOG_RESET
    initr_net,
#endif
    run_main_loop, /* 5 uboot中最重要的函数之一,在这里串口接收到回车进入命令行等待输入命令,如回车,启动kernel */
};

5.1 mmc 初始化

  mmc初始化函数init_mmc实际调用mmc_initialize函数来完成初始化

int mmc_initialize(bd_t *bis)
{
    INIT_LIST_HEAD (&mmc_devices);
    cur_dev_num = 0;
    if (board_mmc_init(bis) < 0) //6818 mmc初始化函数
        cpu_mmc_init(bis);
#ifndef CONFIG_SPL_BUILD
    print_mmc_devices(',');
#endif
    do_preinit(); //preinit
    return 0;
}

5.2 环境变量初始化

static int initr_env(void)
{
    /* initialize environment */
    if (should_load_env())
        env_relocate(); /* 调用env_relocate,最后还是需要调用set_default_env */
    else
        set_default_env(NULL); 
    /* Initialize from environment */
    load_addr = getenv_ulong("loadaddr", 16, load_addr); /* 从环境变量获取加载地址 */
}

  set_default_env函数按照特别格式解析default_environment,并将解析后的内容保存到hash表中

void set_default_env(const char *s) /* env_common.c u-boot\common * /
{
    int flags = 0;

    if (sizeof(default_environment) > ENV_SIZE) {
        puts("*** Error - default environment is too large\n\n");
        return;
    }
    if (s) {
        if (*s == '!') {
            printf("*** Warning - %s, "
                "using default environment\n\n",
                s + 1);
        } else {
            flags = H_INTERACTIVE;
            puts(s);
        }
    } else {
        puts("Using default environment\n\n");
    }
    if (himport_r(&env_htab, (char *)default_environment,
            sizeof(default_environment), '\0', flags,
            0, NULL) == 0)
        error("Environment import failed: errno = %d\n", errno);
    gd->flags |= GD_FLG_ENV_READY;
}

5.3 board_late_init

  board_late_init在board.c kernel\arch\arm\plat-s5p6818\drone,主要完成判断是否需要进入fastboot升级状态以及开始uboot logo显示流程,显示部分函数

void bd_display_run(char *cmd, int bl_duty, int bl_on)
{
    static int display_init = 0;
    if (cmd) {
        run_command(cmd, 0); /* 画logo */
        lcd_draw_boot_logo(CONFIG_FB_ADDR, CFG_DISP_PRI_RESOL_WIDTH,
            CFG_DISP_PRI_RESOL_HEIGHT, CFG_DISP_PRI_SCREEN_PIXEL_BYTE);
    }
    if (!display_init) { /* 6818显示控制器配置 */
        bd_display();
        pwm_init(CFG_LCD_PRI_PWM_CH, 0, 0);
        display_init = 1;
    }
    pwm_config(CFG_LCD_PRI_PWM_CH,TO_DUTY_NS(bl_duty, CFG_LCD_PRI_PWM_FREQ),
        TO_PERIOD_NS(CFG_LCD_PRI_PWM_FREQ));
    if (bl_on)
        pwm_enable(CFG_LCD_PRI_PWM_CH); /* 背光亮 */
}

5.4 run_main_loop

  run_main_loop函数主要内容就是main_loop函数

void main_loop(void)
{
    const char *s;
    modem_init();
    cli_init();
    run_preboot_environment_command();
    s = bootdelay_process();
    if (cli_process_fdt(&s))
        cli_secure_boot_cmd(s);
    autoboot_command(s); /* bootdelay部分在这里完成 */
    cli_loop();
}

  跑到run_main_loop函数,uboot已经到了最后了,接下来就是启动kernel或者进入命令行等待命令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值