嵌入式Linux驱动学习之路(五)u-boot启动流程分析

这里说的u-boot启动流程,值得是从上电开机执行u-boot,到u-boot,到u-boot加载操作系统的过程。这一过程可以分为两个过程,各个阶段的功能如下。

第一阶段的功能:

  • 硬件设备初始化。
  • 加载u-boot第二阶段代码到RAM空间。
  • 设置好栈。
  • 跳转到第二阶段代码入口。

 第二阶段的功能:

  • 初始化本阶段使用的硬件设备。
  • 检查系统内存映射。
  • 将内核从Flash读取到RAM中。
  • 为内核设置启动参数。
  • 调用内核。

CPU有7种模式

ARM中处理器模式

  说明 备注
用户(usr) 正常程序工作模式 此模式下程序不能够访问一些受操作系统保护的系统资源,应用程序也不能直接进行处理器模式的切换。
系统(sys) 用于支持操作系统的特权任务等 与用户模式类似,但具有可以直接切换到其它模式等特权
快中断(fiq) 支持高速数据传输及通道处理 FIQ异常响应时进入此模式
中断(irq) 用于通用中断处理 IRQ异常响应时进入此模式
管理(svc) 操作系统保护代码 系统复位和软件中断响应时进入此模式
中止(abt) 用于支持虚拟内存和/或存储器保护 在ARM7TDMI没有大用处
未定义(und) 支持硬件协处理器的软件仿真 未定义指令异常响应时进入此模式

u-boot启动第一阶段流程

  根据连接器脚本 board/samsung/$(BOARD)/u-boot.lds中指定的链接方式,u-boot代码段第一个链接的是arch/arm/cpu/armv7/start.o,入口是_start,因此u-boot的入口代码在对应的源文件 arch/arm/cpu/armv7/start.S中。

  下面分析start.S的执行

  设置异常向量表

    当一个异常或中断发生时,处理器会把pc指针设置为一个特定的存储器地址。这一地址放在一个被称为向量表(vector table)的特定地址范围内。

    ARM异常向量表

地 址 异常类型 进入模式 说明
 0x00000000  复位 管理模式 复位电平有效时产生
0x00000004 未定义指令 未定义指令模式 遇到ARM处理器无法识别的指令时产生
0x00000008 软件中断 管理模式 SWI指令产生
0x0000000c 预取指令 中止模式 当获取的指令不存在时产生
0x00000010 数据访问 中止模式  当获取的数据不存在时产生
0x00000014 保留 保留 保留
0x00000018 IRQ IRQ模式 中断请求有效,并且CRSR中的1位为0
0x0000001c  FIQ FIQ模式 快读中断请求有效,并且CRSR中的F位为0

    其中,复位异常向量的指令b reset决定u-boot启动后到teset处执行。

.globl _start
_start: 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

_undefined_instruction: .word undefined_instruction
_software_interrupt:	.word software_interrupt
_prefetch_abort:	.word prefetch_abort
_data_abort:		.word data_abort
_not_used:		.word not_used
_irq:			.word irq
_fiq:			.word fiq
_pad:			.word 0x12345678 /* now 16*4=64 */

 CPU进入SVC模式(管理模式)    

/*
* set the cpu to SVC32 mode
*/
mrs    r0, cpsr
bic    r0, r0, #0x1f
orr    r0, r0, #0xd3
msr    cpsr,r0

  注:uboot作为一个bootloader来说,最终目的是为了启动Linux的kernel,在做好准备工作(即初始化硬件,准备好kernel和rootfs等)跳转到kernel之前,本身就要满足一些条件,其中一个条件,就是要求CPU处于SVC模式的。

   (关于满足哪些条件,详情请参考:ARM Linux Kernel Boot Requirements  http://www.arm.linux.org.uk/developer/booting.php

   或者Linux内核文档: kernel_source_rootdocumentationarmooting

   中也是同样的解释:“The CPU must be in SVC mode”)

   所以,uboot在最初的初始化阶段,就将CPU设置为SVC模式,也是最合适的。

   Copy vectors to mask ROM indirect addr(拷贝载体掩模ROM间接地址)  

#if (CONFIG_OMAP34XX)
	/* Copy vectors to mask ROM indirect addr */
	adr	r0, _start		@ r0 <- current position of code
	add	r0, r0, #4		@ skip reset vector
	mov	r2, #64			@ r2 <- size to copy
	add	r2, r0, r2		@ r2 <- source end address
	mov	r1, #SRAM_OFFSET0	@ build vect addr
	mov	r3, #SRAM_OFFSET1
	add	r1, r1, r3
	mov	r3, #SRAM_OFFSET2
	add	r1, r1, r3
next:
	ldmia	r0!, {r3 - r10}		@ copy from source address [r0]
	stmia	r1!, {r3 - r10}		@ copy to   target address [r1]
	cmp	r0, r2			@ until source end address [r2]
	bne	next			@ loop until equal */
#if !defined(CONFIG_SYS_NAND_BOOT) && !defined(CONFIG_SYS_ONENAND_BOOT)
	/* No need to copy/exec the clock code - DPLL adjust already done
	 * in NAND/oneNAND Boot.
	 */
    @这里不需要复制/执行时钟代码数字锁相环调整已经完成在Nand / onenand启动 bl cpy_clk_code @ put dpll adjust code behind vectors #endif /* NAND Boot */ #endif /* the mask ROM code should have PLL and others stable */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif

   关闭MMU和Cache    

/*************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************/
cpu_init_crit:

	bl cache_init    //空函数  直接返回
	
	/*
	 * Invalidate L1 I/D
	 */
	mov	r0, #0			@ set up for MCR
	mcr	p15, 0, r0, c8, c7, 0	@ invalidate TLBs
	mcr	p15, 0, r0, c7, c5, 0	@ invalidate icache

	/*
	 * disable MMU stuff and caches
	 */
	mrc	p15, 0, r0, c1, c0, 0
	bic	r0, r0, #0x00002000	@ clear bits 13 (--V-)
	bic	r0, r0, #0x00000007	@ clear bits 2:0 (-CAM)
	orr	r0, r0, #0x00000002	@ set bit 1 (--A-) Align
	orr	r0, r0, #0x00000800	@ set bit 12 (Z---) BTB
	mcr	p15, 0, r0, c1, c0, 0

	/*
	 * 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.
	 */
	mov	ip, lr			@ persevere link reg across call
	bl	lowlevel_init		@ go setup pll,mux,memory
	mov	lr, ip			@ restore link
	mov	pc, lr			@ back to my caller

   板级初始化

.globl lowlevel_init
lowlevel_init:

	/* use iROM stack in bl2 */
	ldr	sp, =0x02060000      /*设置栈指针*/
	push	{lr}

	/* check reset status 检查系统状态 */
	ldr	r0, =(INF_REG_BASE + INF_REG1_OFFSET)
	ldr	r1, [r0]

	/* Sleep wakeup reset */
	ldr	r2, =S5P_CHECK_SLEEP
	cmp	r1, r2
	beq	wakeup_reset

	/* set CP reset to low */
	ldr	r0, =0x11000C60
	ldr	r1, [r0]
	ldr	r2, =0xFFFFFF0F
	and	r1, r1, r2
	orr	r1, r1, #0x10
	str	r1, [r0]
	ldr	r0, =0x11000C68
	ldr	r1, [r0]
	ldr	r2, =0xFFFFFFF3
	and	r1, r1, r2
	orr	r1, r1, #0x4
	str	r1, [r0]
	ldr	r0, =0x11000C64
	ldr	r1, [r0]
	ldr	r2, =0xFFFFFFFD
	and	r1, r1, r2
	str	r1, [r0]

	/* led (GPM4_0~3) on */    /*点亮LED灯*/
	ldr	r0, =0x110002E0
	ldr	r1, =0x00001111
	str	r1, [r0]
	ldr	r1, =0x0e
	str	r1, [r0, #0x04]

	/* During sleep/wakeup or AFTR mode, pmic_init function is not available
	 * and it causes delays. So except for sleep/wakeup and AFTR mode,
	 * the below function is needed
	 */
/*判断启动方式*/ #if defined(CONFIG_HAS_PMIC)  //未定义 bl pmic_init #endif #if defined(CONFIG_ONENAND)  //未定义 bl onenandcon_init #endif #if defined(NAND_BOOTING)  //未定义 bl nand_asm_init #endif

bl    read_om      //读取启动设备

    设置栈指针、检查系统状态、点亮LED灯。

  判断启动位置  

read_om:      /*读取启动设备*/
	/* Read booting information */
	ldr	r0, =S5PV310_POWER_BASE
	ldr	r1, [r0,#OMR_OFFSET]    
	bic	r2, r1, #0xffffffc1    

	/* NAND BOOT */
@	cmp	r2, #0x0		@ 512B 4-cycle
@	moveq	r3, #BOOT_NAND

@	cmp	r2, #0x2		@ 2KB 5-cycle
@	moveq	r3, #BOOT_NAND

@	cmp	r2, #0x4		@ 4KB 5-cycle	8-bit ECC
@	moveq	r3, #BOOT_NAND

	cmp	r2, #0xA
	moveq	r3, #BOOT_ONENAND

	cmp	r2, #0x10		@ 2KB 5-cycle	16-bit ECC
	moveq	r3, #BOOT_NAND

/*真正觉得启动方式的一个开关。而启动开关只会影响XOM中的值*/
/* SD/MMC BOOT */ cmp r2, #0x4 moveq r3, #BOOT_MMCSD /* eMMC BOOT */ cmp r2, #0x6 moveq r3, #BOOT_EMMC /* eMMC 4.4 BOOT */ cmp r2, #0x8 moveq r3, #BOOT_EMMC_4_4 cmp r2, #0x28 moveq r3, #BOOT_EMMC_4_4 ldr r0, =INF_REG_BASE str r3, [r0, #INF_REG3_OFFSET]      /*将读取到的启动设备结果写入到INFREG3寄存器*/ mov pc, lr

    将读取到的启动设备结果写入到INFREG3寄存器 。

  判断程序是否运行在RAM中

/* when we already run in ram, we don't need to relocate U-Boot.
* and actually, memory controller must be configured before U-Boot
* is running in ram.
*/
ldr	r0, =0xff000fff
bic	r1, pc, r0		/* r0 <- current base addr of code */
ldr	r2, _TEXT_BASE	/* r1 <- original base addr in ram */
bic	r2, r2, r0		/* r0 <- current base addr of code */
cmp	r1, r2			/* compare r0, r1 */
beq	after_copy		/* r0 == r1 then skip sdram init and u-boot.bin loading */

       会比较PC与0xc3e00000中间3位的值,如果相等则代表已经运行在SDRAM,就会跳过SDRAM的初始化。

  初始化时钟、内存、串口初始化   

/* init system clock */
bl	system_clock_init

/* Memory initialize */ bl mem_ctrl_asm_init /* init uart for debug */ bl uart_asm_init

     至此,PLL、SDRAM、uart已全部初始化,启动时要用的最基本的硬件已经准备就绪。

下面是测试一段代码:

#if CONFIG_LL_DEBUG
	mov	r4, #0x4000
.L0:
	sub	r4, r4, #1
	cmp	r4, #0
	bne	.L0

	mov	r0, #'
'
	bl	uart_asm_putc
	mov	r0, #'
'
	bl	uart_asm_putc

	ldr	r1, =0x40000000
	ldr	r2, =0x87654321
	str	r2, [r1]
	str	r2, [r1, #0x04]
	str	r2, [r1, #0x08]
	ldr	r2, =0x55aaaa55
	str	r2, [r1, #0x10]
	nop

	mov	r4, #0xC0000
.L1:
	subs	r4, r4, #1
	bne	.L1

	ldr	r0, [r1]
	bl	uart_asm_putx
	mov	r0, #'.'
	bl	uart_asm_putc

	ldr	r0, [r1, #0x04]
	bl	uart_asm_putx
	mov	r0, #'.'
	bl	uart_asm_putc

	ldr	r0, [r1, #0x08]
	bl	uart_asm_putx
	mov	r0, #'.'
	bl	uart_asm_putc

	ldr	r0, [r1, #0x10]
	bl	uart_asm_putx
	mov	r0, #'>'
	bl	uart_asm_putc
#endif /* CONFIG_LL_DEBUG */ 

     b    1f
v310_1:
    /* init system clock */
    bl    system_clock_init
    /* Memory initialize */
    bl    mem_ctrl_asm_init

1:
    bl    tzpc_init
    b    load_uboot    /*将u-boot的完整代码负责到SDRAM中*/

   将u-boot拷贝到内存中运行

load_uboot:
ldr	r0, =INF_REG_BASE
ldr	r1, [r0, #INF_REG3_OFFSET]
cmp	r1, #BOOT_NAND
beq	nand_boot
cmp	r1, #BOOT_ONENAND
beq	onenand_boot
cmp	r1, #BOOT_MMCSD
beq	mmcsd_boot
cmp	r1, #BOOT_EMMC
beq	emmc_boot
cmp	r1, #BOOT_EMMC_4_4
beq	emmc_boot_4_4
cmp	r1, #BOOT_NOR
beq	nor_boot
cmp	r1, #BOOT_SEC_DEV
beq	mmcsd_boot     

    在判断启动位置那一节中已经获取到了启动类型并存储到 INF_REG3_OFFSET 寄存器中, 这里从获取该寄存器中获取启动类型后直接跳转到相应的节点。我这里是以SD卡启动的。

mmcsd_boot:

#ifdef CONFIG_SMDKC220
//#ifdef CONFIG_CLK_BUS_DMC_200_400
	ldr	r0, =ELFIN_CLOCK_BASE
	ldr	r2, =CLK_DIV_FSYS2_OFFSET
	ldr	r1, [r0, r2]
	orr	r1, r1, #0xf
	str	r1, [r0, r2]
//#endif
#else
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
	ldr	r0, =ELFIN_CLOCK_BASE
	ldr	r2, =CLK_DIV_FSYS2_OFFSET
	ldr	r1, [r0, r2]
	orr	r1, r1, #0xf
	str	r1, [r0, r2]
#endif
#endif
	bl	movi_uboot_copy    //拷贝u-boot到SDRAM
	b	after_copy

   进入到第二阶段

after_copy:

	/* led (GPM4_0~3) on */
	ldr	r0, =0x110002E0
	ldr	r1, =0x0c
	str	r1, [r0, #0x04]

#ifdef CONFIG_SMDKC220
	/* set up C2C */
	ldr	r0, =S5PV310_SYSREG_BASE
	ldr	r2, =GENERAL_CTRL_C2C_OFFSET
	ldr	r1, [r0, r2]
	ldr	r3, =0x4000
	orr	r1, r1, r3
	str	r1, [r0, r2]
#endif

#ifdef CONFIG_ENABLE_MMU
	bl	enable_mmu
#endif

	/* store second boot information in u-boot C level variable */
	ldr	r0, =CONFIG_PHY_UBOOT_BASE
	sub	r0, r0, #8
	ldr	r1, [r0]
	ldr	r0, _second_boot_info
	str	r1, [r0]

	/* Print 'K' */
	ldr	r0, =S5PV310_UART_CONSOLE_BASE
	ldr	r1, =0x4b4b4b4b
	str	r1, [r0, #UTXH_OFFSET]

	ldr	r0, _board_init_f    
	mov	pc, r0

_board_init_f:
	.word board_init_f

_second_boot_info:
	.word second_boot_info

     取函数 _board_init_f 的地址。该函数在 arch/arm/lib/board.c中实现。

  第一阶段结束。

u-boot启动第二阶段流程

  board_init_f 在 arch/arm/lib/board.c 文件中定义。在分析board_init_f函数前先来介绍一些重要的数据结构。

  gd_t结构体

u-boot使用一个结构体gd_t来存储全局数据区的数据。其定义的文件是archarmincludeasmGlobal_data.h。    

typedef    struct    global_data {
    bd_t        *bd;
    unsigned long    flags;
    unsigned long    baudrate;
    unsigned long    have_console;    /* serial_init() was called */
    unsigned long    env_addr;    /* Address  of Environment struct */
    unsigned long    env_valid;    /* Checksum of Environment valid? */
    unsigned long    fb_base;    /* base address of frame buffer */
#ifdef CONFIG_VFD
    unsigned char    vfd_type;    /* display type */
#endif
#ifdef CONFIG_FSL_ESDHC
    unsigned long    sdhc_clk;
#endif
#ifdef CONFIG_AT91FAMILY
    /* "static data" needed by at91's clock.c */
    unsigned long    cpu_clk_rate_hz;
    unsigned long    main_clk_rate_hz;
    unsigned long    mck_rate_hz;
    unsigned long    plla_rate_hz;
    unsigned long    pllb_rate_hz;
    unsigned long    at91_pllb_usb_init;
#endif
#ifdef CONFIG_ARM
    /* "static data" needed by most of timer.c on ARM platforms */
    unsigned long    timer_rate_hz;
    unsigned long    tbl;
    unsigned long    tbu;
    unsigned long long    timer_reset_value;
    unsigned long    lastinc;
#endif
    unsigned long    relocaddr;    /* Start address of U-Boot in RAM */
    phys_size_t    ram_size;    /* RAM size */
    unsigned long    mon_len;    /* monitor len */
    unsigned long    irq_sp;        /* irq stack pointer */
    unsigned long    start_addr_sp;    /* start_addr_stackpointer */
    unsigned long    reloc_off;
#if !(defined(CONFIG_SYS_NO_ICACHE) && defined(CONFIG_SYS_NO_DCACHE))
    unsigned long    tlb_addr;
#endif
    void        **jt;        /* jump table */
    char        env_buf[32];    /* buffer for getenv() before reloc. */
} gd_t;

    u-boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址。

    #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")

    这个指针被放在指定的r8中,这个声明也避免编译器把r8分配给其他变量。

    对于任意想访问全局数据区的代码,只要在其开头加入 DECLARE_GLOBAL_DATA_PTR 一行代码就可以了。

  bd_t结构体

bd_t存放板级相关的全局数据,是gd_t中结构指针成员bd的结构体类型。在 arch/arm/include/u-boot.h 中定义如下:  

typedef struct bd_info {
    int            bi_baudrate;    /* serial console baudrate */
    unsigned long    bi_ip_addr;    /* IP Address */
    ulong            bi_arch_number;    /* unique id for this board */
    ulong            bi_boot_params;    /* where this board expects params */
    struct                /* RAM configuration */
    {
    ulong start;
    ulong size;
    }            bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;

    u-boot启动内核时要给内核传递参数,这时需要使用gd_t、bd_t结构体中的信息来设置标记列表。

  init_sequence数组

u-boot使用一个数组init_sequence来存储大多数开发板都要执行的初始化函数的函数指针。

init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
    arch_cpu_init,        /* basic arch cpu dependent setup */
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
    board_early_init_f,
#endif
    timer_init,        /* initialize timer */
#ifdef CONFIG_FSL_ESDHC
    get_clocks,
#endif
    env_init,        /* initialize environment */
#if defined(CONFIG_S5P6450) && !defined(CONFIG_S5P6460_IP_TEST)
    init_baudrate,        /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
#endif
    console_init_f,        /* stage 1 init of console */
    display_banner,        /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,        /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
    checkboard,        /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
    init_func_i2c,
#endif
    dram_init,        /* configure available RAM banks */
#if defined(CONFIG_CMD_PCI) || defined(CONFIG_PCI)
    arm_pci_init,
#endif
    NULL,
};

     .

  board_init_f()顺序分析

gd_t 数据结构空间分配、回调一组初始化函数、对gd_t数据结构进行初始化、relocate_code(UBOOT重定义代码,即自搬移)。

void board_init_f(ulong bootflag)
{
    bd_t *bd;
    init_fnc_t **init_fnc_ptr;
    gd_t *id;
    ulong addr, addr_sp;

    /* Pointer is writable since we allocated a register for it */
    gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);    /*得到全局数据结构的地址*/

    /* compiler optimization barrier needed for GCC >= 3.4 */
    __asm__ __volatile__("": : :"memory");

    memset((void*)gd, 0, sizeof (gd_t));

    gd->mon_len = _bss_end_ofs;

    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang();
        }
    }

    debug ("monitor len: %08lX
", gd->mon_len);
    /*
     * Ram is setup, size stored in gd !!
     */
    debug ("ramsize: %08lX
", gd->ram_size);
#if defined(CONFIG_SYS_MEM_TOP_HIDE)
    /*
     * Subtract specified amount of memory to hide so that it won't
     * get "touched" at all by U-Boot. By fixing up gd->ram_size
     * the Linux kernel should now get passed the now "corrected"
     * memory size and won't touch it either. This should work
     * for arch/ppc and arch/powerpc. Only Linux board ports in
     * arch/powerpc with bootwrapper support, that recalculate the
     * memory size from the SDRAM controller setup will have to
     * get fixed.
     */
    gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE;
#endif

    addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;

#ifdef CONFIG_LOGBUFFER
#ifndef CONFIG_ALT_LB_ADDR
    /* reserve kernel log buffer */
    addr -= (LOGBUFF_RESERVE);
    debug ("Reserving %dk for kernel logbuffer at %08lx
", LOGBUFF_LEN, addr);
#endif
#endif

#ifdef CONFIG_PRAM
    /*
     * reserve protected RAM
     */
    i = getenv_r("pram", (char *)tmp, sizeof (tmp));
    reg = (i > 0) ? simple_strtoul((const char *)tmp, NULL, 10) : CONFIG_PRAM;
    addr -= (reg << 10);        /* size is in kB */
    debug ("Reserving %ldk for protected RAM at %08lx
", reg, addr);
#endif /* CONFIG_PRAM */

#if !(defined(CONFIG_SYS_NO_ICACHE) && defined(CONFIG_SYS_NO_DCACHE))
    /* reserve TLB table */
    addr -= (4096 * 4);

    /* round down to next 64 kB limit */
    addr &= ~(0x10000 - 1);

    gd->tlb_addr = addr;
    debug ("TLB table at: %08lx
", addr);
#endif

    /* round down to next 4 kB limit */
    addr &= ~(4096 - 1);
    debug ("Top of RAM usable for U-Boot at: %08lx
", addr);

#ifdef CONFIG_VFD
#    ifndef PAGE_SIZE
#      define PAGE_SIZE 4096
#    endif
    /*
     * reserve memory for VFD display (always full pages)
     */
    addr -= vfd_setmem(addr);
    gd->fb_base = addr;
#endif /* CONFIG_VFD */

#ifdef CONFIG_LCD
    /* reserve memory for LCD display (always full pages) */
    addr = lcd_setmem(addr);
    gd->fb_base = addr;
#endif /* CONFIG_LCD */

    /*
     * reserve memory for U-Boot code, data & bss
     * round down to next 4 kB limit
     */
    addr -= gd->mon_len;
    addr &= ~(4096 - 1);

#if defined(CONFIG_S5P) || defined(CONFIG_S5P6450)
    addr = CONFIG_SYS_LOAD_ADDR;
#endif

    debug ("Reserving %ldk for U-Boot at: %08lx
", gd->mon_len >> 10, addr);

#ifndef CONFIG_PRELOADER
    /*
     * reserve memory for malloc() arena
     */
    addr_sp = addr - TOTAL_MALLOC_LEN;
    debug ("Reserving %dk for malloc() at: %08lx
",
            TOTAL_MALLOC_LEN >> 10, addr_sp);
    /*
     * (permanently) allocate a Board Info struct
     * and a permanent copy of the "global" data
     */
    addr_sp -= sizeof (bd_t);
    bd = (bd_t *) addr_sp;
    gd->bd = bd;
    debug ("Reserving %zu Bytes for Board Info at: %08lx
",
            sizeof (bd_t), addr_sp);
    addr_sp -= sizeof (gd_t);
    id = (gd_t *) addr_sp;
    debug ("Reserving %zu Bytes for Global Data at: %08lx
",
            sizeof (gd_t), addr_sp);

    /* setup stackpointer for exeptions */
    gd->irq_sp = addr_sp;
#ifdef CONFIG_USE_IRQ
    addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
    debug ("Reserving %zu Bytes for IRQ stack at: %08lx
",
        CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);
#endif

    /* leave 3 words for abort-stack    */
    addr_sp -= 3;

    /* 8-byte alignment for ABI compliance */
    addr_sp &= ~0x07;
#else
    addr_sp += 128;    /* leave 32 words for abort-stack   */
    gd->irq_sp = addr_sp;
#endif

    debug ("New Stack Pointer is: %08lx
", addr_sp);

#ifdef CONFIG_POST
    post_bootmode_init();
    post_run(NULL, POST_ROM | post_bootmode_get(0));
#endif

    gd->bd->bi_baudrate = gd->baudrate;
    /* Ram ist board specific, so move it to board code ... */
    dram_init_banksize();
    display_dram_config();    /* and display it */

    gd->relocaddr = addr;
    gd->start_addr_sp = addr_sp;
    gd->reloc_off = addr - _TEXT_BASE;
    debug ("relocation Offset is: %08lx
", gd->reloc_off);
    memcpy(id, (void *)gd, sizeof (gd_t));

    relocate_code(addr_sp, id, addr);
    /* NOTREACHED - relocate_code() does not return */
}

    在relocate_code中会调用board_init_r()函数

     

  board_init_f()顺序分析

/************************************************************************
 *
 * This is the next part if the initialization sequence: we are now
 * running from RAM and have a "normal" C environment, i. e. global
 * data can be written, BSS has been cleared, the stack size in not
 * that critical any more, etc.
 *
 ************************************************************************
 */

void board_init_r(gd_t *id, ulong dest_addr)
{
    char *s;
    bd_t *bd;
    ulong malloc_start;
#if !defined(CONFIG_SYS_NO_FLASH)
    ulong flash_size;
#endif

    gd = id;
    bd = gd->bd;

    gd->flags |= GD_FLG_RELOC;    /* tell others: relocation done */

    monitor_flash_len = _bss_start_ofs;
    debug ("monitor flash len: %08lX
", monitor_flash_len);
    board_init();    /* Setup chipselects */

#ifdef CONFIG_SERIAL_MULTI
    //serial_initialize();
#endif

    debug ("Now running in RAM - U-Boot at: %08lx
", dest_addr);

#ifdef CONFIG_LOGBUFFER
    logbuff_init_ptrs();
#endif
#ifdef CONFIG_POST
    post_output_backlog();
#endif

    /* The Malloc area is immediately below the monitor copy in DRAM */
    malloc_start = dest_addr - TOTAL_MALLOC_LEN;
    mem_malloc_init(malloc_start, TOTAL_MALLOC_LEN);

#if !defined(CONFIG_SYS_NO_FLASH)
    puts("FLASH:	");

    if ((flash_size = flash_init()) > 0) {
# ifdef CONFIG_SYS_FLASH_CHECKSUM
        print_size(flash_size, "");
        /*
         * Compute and print flash CRC if flashchecksum is set to 'y'
         *
         * NOTE: Maybe we should add some WATCHDOG_RESET()? XXX
         */
        s = getenv("flashchecksum");
        if (s && (*s == 'y')) {
            printf("  CRC: %08X",
                crc32 (0, (const unsigned char *) CONFIG_SYS_FLASH_BASE, flash_size)
            );
        }
        putc('
');
# else    /* !CONFIG_SYS_FLASH_CHECKSUM */
        print_size(flash_size, "
");
# endif /* CONFIG_SYS_FLASH_CHECKSUM */
    } else {
        puts(failed);
        hang();
    }
#endif

#if defined(CONFIG_CMD_NAND)
    puts("NAND:	");
    nand_init();        /* go init the NAND */
#endif

#if defined(CONFIG_CMD_ONENAND)
    onenand_init();
#endif

#ifdef CONFIG_GENERIC_MMC
    mmc_initialize(bd);
#endif

#ifdef CONFIG_HAS_DATAFLASH
    AT91F_DataflashInit();
    dataflash_print_info();
#endif

    /* initialize environment */
    env_relocate();    //初始化环境变量

#ifdef CONFIG_VFD
    /* must do this after the framebuffer is allocated */
    drv_vfd_init();
#endif /* CONFIG_VFD */

    /* IP Address */
    gd->bd->bi_ip_addr = getenv_IPaddr("ipaddr");

    stdio_init();    /* get the devices list going. */

    jumptable_init();

#if defined(CONFIG_API)
    /* Initialize API */
    api_init();
#endif

    //console_init_r();    /* fully init console as a device */

#if defined(CONFIG_ARCH_MISC_INIT)
    /* miscellaneous arch dependent initialisations */
    arch_misc_init();
#endif
#if defined(CONFIG_MISC_INIT_R)
    /* miscellaneous platform dependent initialisations */
    misc_init_r();
#endif

     /* set up exceptions */
    interrupt_init();
    /* enable exceptions */
    enable_interrupts();

    /* Perform network card initialisation if necessary */
#if defined(CONFIG_DRIVER_SMC91111) || defined(CONFIG_DRIVER_LAN91C96)
    /* XXX: this needs to be moved to board init */
    if (getenv("ethaddr")) {
        uchar enetaddr[6];
        eth_getenv_enetaddr("ethaddr", enetaddr);
        smc_set_mac_addr(enetaddr);
    }
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */

#if defined(CONFIG_DRIVER_DM9000)
    /* XXX: this needs to be moved to board init */
    if (getenv("ethaddr")) {
        uchar enetaddr[6];
        eth_getenv_enetaddr("ethaddr", enetaddr);
        dm9000_set_mac_addr(enetaddr);
    }
#endif

    /* Initialize from environment */
    if ((s = getenv("loadaddr")) != NULL) {
        load_addr = simple_strtoul(s, NULL, 16);
    }
#if defined(CONFIG_CMD_NET)
    if ((s = getenv("bootfile")) != NULL) {
        copy_filename(BootFile, s, sizeof (BootFile));
    }
#endif

#ifdef BOARD_LATE_INIT
    board_late_init();
#endif

#ifdef CONFIG_BITBANGMII
    bb_miiphy_init();
#endif
#if defined(CONFIG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
    puts("Net:	");
#endif
    eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)
    debug ("Reset Ethernet PHY
");
    reset_phy();
#endif
#endif

#ifdef CONFIG_POST
    post_run(NULL, POST_RAM | post_bootmode_get(0));
#endif

#if defined(CONFIG_PRAM) || defined(CONFIG_LOGBUFFER)
    /*
     * Export available size of memory for Linux,
     * taking into account the protected RAM at top of memory
     */
    {
        ulong pram;
        uchar memsz[32];
#ifdef CONFIG_PRAM
        char *s;

        if ((s = getenv("pram")) != NULL) {
            pram = simple_strtoul(s, NULL, 10);
        } else {
            pram = CONFIG_PRAM;
        }
#else
        pram=0;
#endif
#ifdef CONFIG_LOGBUFFER
#ifndef CONFIG_ALT_LB_ADDR
        /* Also take the logbuffer into account (pram is in kB) */
        pram += (LOGBUFF_LEN+LOGBUFF_OVERHEAD)/1024;
#endif
#endif
        sprintf((char *)memsz, "%ldk", (bd->bi_memsize / 1024) - pram);
        setenv("mem", (char *)memsz);
    }
#endif

    /* main_loop() can return to retry autoboot, if so just run it again. */
    for (;;) {
        main_loop();
    }

    /* NOTREACHED - no way out of command loop except booting */
}

    完成的功能:使能Cache、 板子初始化、 串口初始化、 外存初始化、 环境变量初始化、 控制台初始哗、 中断使能、 以太网初始化、 进入main_loop(),等待命令或自动加载内核。

   main_loop函数分析

    main_loop函数在 common/main.c中定义,做的都是与平台无关的工作,主要是包含初始化启动次数限制机制、设置软件版本号、打印启动信息、解析命令等。  

    设置启动次数有关参数

      在进入main_loop()函数后,首先根据配置加载已经保留的启动次数,并且根据配置判断是否超过启动次数,代码如下:

void main_loop (void)
{
#ifndef CONFIG_SYS_HUSH_PARSER
	static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
	int len;
	int rc = 1;
	int flag;
#endif

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
	char *s;
	int bootdelay;
#endif
#ifdef CONFIG_PREBOOT
	char *p;
#endif
#ifdef CONFIG_BOOTCOUNT_LIMIT
	unsigned long bootcount = 0;
	unsigned long bootlimit = 0;
	char *bcs;
	char bcs_set[16];
#endif /* CONFIG_BOOTCOUNT_LIMIT */

#if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO)
	ulong bmp = 0;		/* default bitmap */
	extern int trab_vfd (ulong bitmap);

#ifdef CONFIG_MODEM_SUPPORT
	if (do_mdm_init)
		bmp = 1;	/* alternate bitmap */
#endif
	trab_vfd (bmp);
#endif	/* CONFIG_VFD && VFD_TEST_LOGO */

#ifdef CONFIG_BOOTCOUNT_LIMIT
	bootcount = bootcount_load();    //加载保存的启动次数至变量bootcount
	bootcount++;
	bootcount_store (bootcount);    //将启动次数加1后重新保存。
	sprintf (bcs_set, "%lu", bootcount);  //打印启动次数
	setenv ("bootcount", bcs_set);
	bcs = getenv ("bootlimit");      //读出启动次数现在变量
	bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;  //实现启动限制功能
#endif /* CONFIG_BOOTCOUNT_LIMIT */

    启动Modern功能

      如果系统有modern,打开此功能可以接受其他用户通过电话网络的拨号请求。    

#ifdef CONFIG_MODEM_SUPPORT
	debug ("DEBUG: main_loop:   do_mdm_init=%d
", do_mdm_init);
	if (do_mdm_init) {
		char *str = strdup(getenv("mdm_cmd"));
		setenv ("preboot", str);  /* set or delete definition */
		if (str != NULL)
			free (str);
		mdm_init(); /* wait for modem connection */
	}
#endif  /* CONFIG_MODEM_SUPPORT */

     设置版本号、初始化命令自动完成等功能

      设置u-boot版本号、初始化命令自动化完成功能等。代码如下:

#ifdef CONFIG_VERSION_VARIABLE
    {
        extern char version_string[];

        setenv ("ver", version_string);  /* set version variable */ /*设置版本号*/ 
    }
#endif /* CONFIG_VERSION_VARIABLE */

#ifdef CONFIG_SYS_HUSH_PARSER
    u_boot_hush_start ();
#endif

#if defined(CONFIG_HUSH_INIT_VAR)
    hush_init_var ();
#endif

#ifdef CONFIG_AUTO_COMPLETE
    install_auto_complete();      /*初始化命令自动完成*/
#endif

#ifdef CONFIG_PREBOOT
    if ((p = getenv ("preboot")) != NULL) {
# ifdef CONFIG_AUTOBOOT_KEYED
        int prev = disable_ctrlc(1);    /* disable Control C checking */ /*关闭 ctrl + c 组合键*/
# endif

# ifndef CONFIG_SYS_HUSH_PARSER
        run_command (p, 0);
# else
        parse_string_outer(p, FLAG_PARSE_SEMICOLON |
                    FLAG_EXIT_FROM_LOOP);
# endif

# ifdef CONFIG_AUTOBOOT_KEYED
        disable_ctrlc(prev);    /* restore Control C checking */  /* 恢复 ctrl + c 组合键*/
# endif
    }
#endif /* CONFIG_PREBOOT */

    设置启动延时和启动菜单

      进入主循环之前,如果配置了启动延时功能,需要等待用户从串口或网络接口输入。如果用户按下任意键打断启动流程,则向终端打印一个启动菜单。      

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
	s = getenv ("bootdelay");
	bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

	debug ("### main_loop entered: bootdelay=%d

", bootdelay);

# ifdef CONFIG_BOOT_RETRY_TIME
	init_cmd_timeout ();
# endif	/* CONFIG_BOOT_RETRY_TIME */

#ifdef CONFIG_POST
	if (gd->flags & GD_FLG_POSTFAIL) {
		s = getenv("failbootcmd");
	}
	else
#endif /* CONFIG_POST */
#ifdef CONFIG_BOOTCOUNT_LIMIT      /* 检查是否超出启动次数限制 */
	if (bootlimit && (bootcount > bootlimit)) {
		printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.
",
		        (unsigned)bootlimit);
		s = getenv ("altbootcmd");
	}
	else
#endif /* CONFIG_BOOTCOUNT_LIMIT */
		s = getenv ("bootcmd");    /* 获取启动命令参数 */

	debug ("### main_loop: bootcmd="%s"
", s ? s : "<UNDEFINED>");

	if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
# ifdef CONFIG_AUTOBOOT_KEYED
		int prev = disable_ctrlc(1);	/* disable Control C checking */  /* 关闭 ctrl + c 组合键 */
# endif

# ifndef CONFIG_SYS_HUSH_PARSER
		run_command (s, 0);      /* 运行启动命令 */
# else
		parse_string_outer(s, FLAG_PARSE_SEMICOLON |
				    FLAG_EXIT_FROM_LOOP);
# endif

# ifdef CONFIG_AUTOBOOT_KEYED
		disable_ctrlc(prev);	/* restore Control C checking */    /* 打开ctrl + c 组合键 */
# endif
	}

# ifdef CONFIG_MENUKEY
	if (menukey == CONFIG_MENUKEY) {
	    s = getenv("menucmd");
	    if (s) {
# ifndef CONFIG_SYS_HUSH_PARSER
		run_command (s, 0);
# else
		parse_string_outer(s, FLAG_PARSE_SEMICOLON |
				    FLAG_EXIT_FROM_LOOP);
# endif
	    }
	}
#endif /* CONFIG_MENUKEY */
#endif /* CONFIG_BOOTDELAY */
 

      .

    执行命令循环

	/*
	 * Main Loop for Monitor Command Processing
	 */
#ifdef CONFIG_SYS_HUSH_PARSER
	parse_file_outer();
	/* This point is never reached */
	for (;;);
#else
	for (;;) {
#ifdef CONFIG_BOOT_RETRY_TIME
		if (rc >= 0) {
			/* Saw enough of a valid command to
			 * restart the timeout.
			 */
			reset_cmd_timeout();
		}
#endif
		len = readline (CONFIG_SYS_PROMPT);

		flag = 0;	/* assume no special flags for now */
		if (len > 0)
			strcpy (lastcommand, console_buffer);
		else if (len == 0)
			flag |= CMD_FLAG_REPEAT;
#ifdef CONFIG_BOOT_RETRY_TIME
		else if (len == -2) {
			/* -2 means timed out, retry autoboot
			 */
			puts ("
Timed out waiting for command
");
# ifdef CONFIG_RESET_TO_RETRY
			/* Reinit board to run initialization code again */
			do_reset (NULL, 0, 0, NULL);
# else
			return;		/* retry autoboot */
# endif
		}
#endif

		if (len == -1)
			puts ("<INTERRUPT>
");
		else
			rc = run_command (lastcommand, flag);

		if (rc <= 0) {    
			/* invalid command or not repeatable, forget it */ /* 无效命令或者不可重复执行 */
			lastcommand[0] = 0;
		}
	}
#endif /*CONFIG_SYS_HUSH_PARSER*/

 <完>