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

2022-12-11,,,,

这里说的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_root\documentation\arm\booting

   中也是同样的解释:“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, #'\r'
bl uart_asm_putc
mov r0, #'\n'
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来存储全局数据区的数据。其定义的文件是arch\arm\include\asm\Global_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[]; /* 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, , 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)() != ) {
hang();
}
} debug ("monitor len: %08lX\n", gd->mon_len);
/*
* Ram is setup, size stored in gd !!
*/
debug ("ramsize: %08lX\n", 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\n", LOGBUFF_LEN, addr);
#endif
#endif #ifdef CONFIG_PRAM
/*
* reserve protected RAM
*/
i = getenv_r("pram", (char *)tmp, sizeof (tmp));
reg = (i > ) ? simple_strtoul((const char *)tmp, NULL, ) : CONFIG_PRAM;
addr -= (reg << ); /* size is in kB */
debug ("Reserving %ldk for protected RAM at %08lx\n", reg, addr);
#endif /* CONFIG_PRAM */ #if !(defined(CONFIG_SYS_NO_ICACHE) && defined(CONFIG_SYS_NO_DCACHE))
/* reserve TLB table */
addr -= ( * ); /* round down to next 64 kB limit */
addr &= ~(0x10000 - ); gd->tlb_addr = addr;
debug ("TLB table at: %08lx\n", addr);
#endif /* round down to next 4 kB limit */
addr &= ~( - );
debug ("Top of RAM usable for U-Boot at: %08lx\n", addr); #ifdef CONFIG_VFD
# ifndef PAGE_SIZE
# define PAGE_SIZE
# 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 &= ~( - ); #if defined(CONFIG_S5P) || defined(CONFIG_S5P6450)
addr = CONFIG_SYS_LOAD_ADDR;
#endif debug ("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> , addr); #ifndef CONFIG_PRELOADER
/*
* reserve memory for malloc() arena
*/
addr_sp = addr - TOTAL_MALLOC_LEN;
debug ("Reserving %dk for malloc() at: %08lx\n",
TOTAL_MALLOC_LEN >> , 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\n",
sizeof (bd_t), addr_sp);
addr_sp -= sizeof (gd_t);
id = (gd_t *) addr_sp;
debug ("Reserving %zu Bytes for Global Data at: %08lx\n",
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\n",
CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);
#endif /* leave 3 words for abort-stack */
addr_sp -= ; /* 8-byte alignment for ABI compliance */
addr_sp &= ~0x07;
#else
addr_sp += ; /* leave 32 words for abort-stack */
gd->irq_sp = addr_sp;
#endif debug ("New Stack Pointer is: %08lx\n", addr_sp); #ifdef CONFIG_POST
post_bootmode_init();
post_run(NULL, POST_ROM | post_bootmode_get());
#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\n", 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\n", monitor_flash_len);
board_init(); /* Setup chipselects */ #ifdef CONFIG_SERIAL_MULTI
//serial_initialize();
#endif debug ("Now running in RAM - U-Boot at: %08lx\n", 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:\t"); if ((flash_size = flash_init()) > ) {
# 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 (, (const unsigned char *) CONFIG_SYS_FLASH_BASE, flash_size)
);
}
putc('\n');
# else /* !CONFIG_SYS_FLASH_CHECKSUM */
print_size(flash_size, "\n");
# endif /* CONFIG_SYS_FLASH_CHECKSUM */
} else {
puts(failed);
hang();
}
#endif #if defined(CONFIG_CMD_NAND)
puts("NAND:\t");
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[];
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[];
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, );
}
#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:\t");
#endif
eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)
debug ("Reset Ethernet PHY\n");
reset_phy();
#endif
#endif #ifdef CONFIG_POST
post_run(NULL, POST_RAM | post_bootmode_get());
#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[];
#ifdef CONFIG_PRAM
char *s; if ((s = getenv("pram")) != NULL) {
pram = simple_strtoul(s, NULL, );
} else {
pram = CONFIG_PRAM;
}
#else
pram=;
#endif
#ifdef CONFIG_LOGBUFFER
#ifndef CONFIG_ALT_LB_ADDR
/* Also take the logbuffer into account (pram is in kB) */
pram += (LOGBUFF_LEN+LOGBUFF_OVERHEAD)/;
#endif
#endif
sprintf((char *)memsz, "%ldk", (bd->bi_memsize / ) - 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\n", 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(); /* disable Control C checking */ /*关闭 ctrl + c 组合键*/
# endif # ifndef CONFIG_SYS_HUSH_PARSER
run_command (p, );
# 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\n\n", 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.\n",
(unsigned)bootlimit);
s = getenv ("altbootcmd");
}
else
#endif /* CONFIG_BOOTCOUNT_LIMIT */
s = getenv ("bootcmd");    /* 获取启动命令参数 */ debug ("### main_loop: bootcmd=\"%s\"\n", 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 ("\nTimed out waiting for command\n");
# 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>\n");
else
rc = run_command (lastcommand, flag); if (rc <= 0) {    
/* invalid command or not repeatable, forget it */ /* 无效命令或者不可重复执行 */
lastcommand[0] = 0;
}
}
#endif /*CONFIG_SYS_HUSH_PARSER*/

<完>

嵌入式Linux驱动学习之路(五)u-boot启动流程分析的相关教程结束。

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

下载本文的Word格式文档,以方便收藏与打印。