一、前言

老大给我布置了一个任务:某某项目uboot开发之usb增强ic驱动。

不知道大家看到这个任务懵不懵,反正我最开始是蒙的。后来又问了一下,才明白到底要做啥。

任务是这样的:因为这个项目的usb线比较长,为了避免usb升级的时候读取u盘失败,所以我们采取的方案是硬件提供一个有i2c支持的usb增强ic。因此,我就需要在车机界面点击升级,车机重启进入uboot启动流程的时候去调用我写的驱动去初始化这个i2c,以此保证usb能正常读取到u盘内容,完成升级。

所以,学习uboot的启动流程就很有必要了。

当然,笔者写这篇博客的意思只是想记录一下uboot的启动流程,而不是纠结于某个点,所以一些地方介绍的不清楚还请海涵。

Uboot版本:2014.4

2014.4版本uboot启动至命令行几个重要函数为:_start,_main,board_init_f,relocate_code,board_init_r,main_loop

二、入口函数——_start

对uboot启动流程有过一定了解的朋友们都知道,uboot开始运行的第一个函数,也就是入口函数_start。

对于任何程序,_start都是由链接脚本u-boot.lds决定的。至于你的uboot使用哪一个lds文件,则需要看配置,搜索CONFIG_SYS_LDSCRIPT这个宏会有发现。

第一段注释,翻译:如果板子代码显式地指定了LDSCRIPT或CONFIG SYS LDSCRIPT,则使用它(如果没有,则失败)。否则,在一个标准的地方搜索链接器脚本。

因此,找到板子的配置代码(uboot/include/configs/*.h),搜索CONFIG_SYS_LDSCRIPT,结果发现并没有定义。

然后又看第二段注释,翻译:如果没有指定的链接脚本,我们会在很多地方寻找它。

这里我并不是特别确定我的板子的标准链接器脚本在哪里,只能告诉大家:编译之后,编译系统会把这个脚本拷贝uboot的根目录。

内容如下:

从11行代码看,入口函数_start是在arch/arm/cpu/armv7/start.S中,那感觉u-boot.lds链接脚本也在那儿,这里就不继续探讨这个问题了,我们来看_start入口函数。

.globl _start
_start: b   resetldr pc, _undefined_instructionldr pc, _software_interruptldr pc, _prefetch_abortldr pc, _data_abortldr pc, _not_usedldr pc, _irqldr pc, _fiq
#ifdef CONFIG_SPL_BUILD
_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 */
#else
.globl _undefined_instruction
_undefined_instruction: .word undefined_instruction
.globl _software_interrupt
_software_interrupt:    .word software_interrupt
.globl _prefetch_abort
_prefetch_abort:    .word prefetch_abort
.globl _data_abort
_data_abort:        .word data_abort
.globl _not_used
_not_used:      .word not_used
.globl _irq
_irq:           .word irq
.globl _fiq
_fiq:           .word fiq
_pad:           .word 0x12345678 /* now 16*4=64 */</span>
<span style="font-size:14px;">#endif  /* CONFIG_SPL_BUILD */.global _end_vect
_end_vect:.balignl 16,0xdeadbeef

.global声明_start为全局符号,_start就会被连接器链接到,也就是链接脚本中的入口地址了。

继续看,就是arm中最常见的异常向量表了,下面是我根据异常向量表的定义及相关知识做的一个表格。

地址

异常

进入模式

描述

0x00000000

复位

管理模式

复位电平有效时,产生复位异常,程序跳转到复位处理程序处执行

0x00000004

未定义指令

未定义模式

遇到不能处理的指令时,产生未定义指令异常

0x00000008

软件中断

管理模式

执行SWI指令产生,用于用户模式下的程序调用特权操作指令

0x0000000c

预存指令

中止模式

处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常

0x00000010

数据操作

中止模式

处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常

0x00000014

未使用

未使用

未使用

0x00000018

IRQ

IRQ

外部中断请求有效,且CPSR中的I位为0时,产生IRQ异常

0x0000001c

FIQ

FIQ

快速中断请求引脚有效,且CPSR中的F位为0时,产生FIQ异常

在arm中,每种异常都占4个字节。

“b reset”和“ldr pc, **”这两条指令,其实都是跳转的意思,当异常发生,就会跳转到对应异常处理函数执行异常处理。

继续往下看,就是关于异常处理函数的定义。

接下来定义的_end_vect中用.balignl来指定接下来的代码要16字节对齐,空缺的用0xdeadbeef,方便更加高效的访问内存。

继续看start.S文件。

#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:.word   0x0badc0de/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:.word 0x0badc0de
#endif/* IRQ stack memory (calculated at run-time) + 8 bytes */
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:.word    0x0badc0de

通过宏的意思就能明白,如果定义了使用中断,也就是说uboot如果使用了中断,这里就会声明irq中断和fiq中断的栈首地址。

这里的0x0badc0de是一个非法值,会在interrupt_init函数中被重新计算。

继续看start.S文件。

/** the actual reset code*/reset:bl  save_boot_params/** disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,* except if in HYP mode already*/mrs   r0, cpsrand r1, r0, #0x1f       @ mask mode bitsteq    r1, #0x1a       @ test for HYP modebicne   r0, r0, #0x1f       @ clear all mode bitsorrne r0, r0, #0x13       @ set SVC modeorr  r0, r0, #0xc0       @ disable FIQ and IRQmsr   cpsr,r0

显然,这里就来到reset函数,也就是所谓的复位异常处理函数。

当上电或者重启,都是调到这里执行。复位异常处理函数做的第一件事情是“bl    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)bx   lr          @ back to my caller
ENDPROC(save_boot_params).weak  save_boot_params

从汇编代码内容来看,什么也没有做。注释的意思也差不多:堆栈指针目前尚未初始化,即使使用-O0编译也不要保存任何内容。

.waek这里是做什么我不清楚,有兴趣的可以去网上搜索一下。

回到reset。

接下来reset执行7条指令,修改cpsr寄存器,设置处理器进入svc模式,并且关掉irq和fiq。

继续看start.S文件。

/** Setup vector:* (OMAP4 spl TEXT_BASE is not 32 byte aligned.* Continue to use ROM code vector only in OMAP4 spl)*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))/* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */mrc  p15, 0, r0, c1, c0, 0   @ Read CP15 SCTRL Registerbic  r0, #CR_V       @ V = 0mcr    p15, 0, r0, c1, c0, 0   @ Write CP15 SCTRL Register/* Set vector address in CP15 VBAR register */ldr   r0, =_startmcr p15, 0, r0, c12, c0, 0  @Set VBAR
#endif/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INITbl cpu_init_cp15bl cpu_init_crit
#endifbl    _main

从代码内容和注释来看,if语句内做的事情大概与cp15协处理器有关,这里设置了处理器的异常向量入口地址为_start。

这里需要注意,ARM默认的异常向量表入口在0x0地址,uboot的运行介质(norflash nandflash sram等)映射地址可能不在0x0起始的地址,所以需要修改异常向量。

继续往下看,代码跳转到了cpu_init_cp15函数。

/*************************************************************************** cpu_init_cp15** Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless* CONFIG_SYS_ICACHE_OFF is defined.**************************************************************************/
ENTRY(cpu_init_cp15)/** Invalidate L1 I/D*/mov  r0, #0          @ set up for MCRmcr    p15, 0, r0, c8, c7, 0   @ invalidate TLBsmcr   p15, 0, r0, c7, c5, 0   @ invalidate icachemcr p15, 0, r0, c7, c5, 6   @ invalidate BP arraymcr     p15, 0, r0, c7, c10, 4    @ DSBmcr     p15, 0, r0, c7, c5, 4 @ ISB/** disable MMU stuff and caches*/mrc p15, 0, r0, c1, c0, 0bic    r0, r0, #0x00002000 @ clear bits 13 (--V-)bic  r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)orr r0, r0, #0x00000002 @ set bit 1 (--A-) Alignorr    r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
#ifdef CONFIG_SYS_ICACHE_OFFbic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
#elseorr    r0, r0, #0x00001000 @ set bit 12 (I) I-cache
#endifmcr   p15, 0, r0, c1, c0, 0#ifdef CONFIG_ARM_ERRATA_716044mrc p15, 0, r0, c1, c0, 0   @ read system control registerorr  r0, r0, #1 << 11  @ set bit #11mcr   p15, 0, r0, c1, c0, 0   @ write system control register
#endif#if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))mrc   p15, 0, r0, c15, c0, 1  @ read diagnostic registerorr  r0, r0, #1 << 4       @ set bit #4mcr    p15, 0, r0, c15, c0, 1  @ write diagnostic register
#endif#ifdef CONFIG_ARM_ERRATA_743622mrc    p15, 0, r0, c15, c0, 1  @ read diagnostic registerorr  r0, r0, #1 << 6       @ set bit #6mcr    p15, 0, r0, c15, c0, 1  @ write diagnostic register
#endif#ifdef CONFIG_ARM_ERRATA_751472mrc    p15, 0, r0, c15, c0, 1  @ read diagnostic registerorr  r0, r0, #1 << 11  @ set bit #11mcr   p15, 0, r0, c15, c0, 1  @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_761320mrc  p15, 0, r0, c15, c0, 1  @ read diagnostic registerorr  r0, r0, #1 << 21  @ set bit #21mcr   p15, 0, r0, c15, c0, 1  @ write diagnostic register
#endifmov   pc, lr          @ back to my caller

cpu_init_cp15函数是配置cp15协处理器相关寄存器来关掉处理器的mmu以及tlb。

如果没有定义CONFIG_SYS_ICACHE_OFF则会打开icache。

“back to my caller”回到我的调用者,“bl cpu_init_crit”,显然代码又跳转到了cpu_init_crit函数执行。

/*************************************************************************** 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

从注释来看:会调用b命令跳转到lowlevel_init函数中,去设置初始化(时钟)、mux(好像是数据选择器)、memory(内存)。

从网上看到的资料所说:“如果不是从mem启动,这里会做memory初始化(比如ddr),方便后续拷贝到mem中运行。”

lowlevel_init函数就不细究了。

从cpu_init_crit回去,整个_start函数的工作也就做完了。他最后的代码是“bl _main”,也就是跳转到_main函数执行。

总结一下_start工作:

1、初始化异常向量表,q切换到svc模式,关中断;

2、配置cp15,初始化mmu cache tlb;

3、板级初始化,pll memory初始化。

三、_main函数第一段

_main函数在arch/arm/lib/路径下,可以看到该路径下有crt0.S和crt0_64.S两个文件,这时候该怎么区分项目用哪个文件呢?有两个方法。

1、查看Makefile,可以看到Makefile中有一个条件,“ifdef CONFIG_ARM64”,只有定义了这个才会使用crt0_64.S。

2、直接编译uboot,看看那个文件生成了.o文件。

显然,这里用的是crt0.S。

/** This file handles the target-independent stages of the U-Boot* start-up where a C runtime environment is needed. Its entry point* is _main and is branched into from the target's start.S file.** _main execution sequence is:** 1. Set up initial environment for calling board_init_f().*    This environment only provides a stack and a place to store*    the GD ('global data') structure, both located in some readily*    available RAM (SRAM, locked cache...). In this context, VARIABLE*    global data, initialized or not (BSS), are UNAVAILABLE; only*    CONSTANT initialized data are available.** 2. Call board_init_f(). This function prepares the hardware for*    execution from system RAM (DRAM, DDR...) As system RAM may not*    be available yet, , board_init_f() must use the current GD to*    store any data which must be passed on to later stages. These*    data include the relocation destination, the future stack, and*    the future GD location.** (the following applies only to non-SPL builds)** 3. Set up intermediate environment where the stack and GD are the*    ones allocated by board_init_f() in system RAM, but BSS and*    initialized non-const data are still not available.** 4. Call relocate_code(). This function relocates U-Boot from its*    current location into the relocation destination computed by*    board_init_f().** 5. Set up final environment for calling board_init_r(). This*    environment has BSS (initialized to 0), initialized non-const*    data (initialized to their intended value), and stack in system*    RAM. GD has retained values set by board_init_f(). Some CPUs*    have some work left to do at this point regarding memory, so*    call c_runtime_cpu_setup.** 6. Branch to board_init_r().*/

打开crt0.S,入目而来的就是这么长一段注释。翻译如下:

此文件处理U-Boot的目标无关阶段在需要C运行时环境的情况下启动。它的切入点是_main,并且从目标的start.S文件分支。_main执行顺序为:
1.设置用于调用board_init_f()的初始环境。此环境仅提供堆栈和存储位置GD(“全局数据”)结构,两者都位于可用的RAM(SRAM,锁定的缓存...)。在这种情况下,VARIABLE初始化或未初始化的全局数据(BSS)不可用;只要常量初始化数据可用。
2.调用board_init_f()。此功能为从系统RAM(DRAM,DDR ...)执行仍然可用,board_init_f()必须使用当前GD来存储必须传递到后续阶段的所有数据。这些数据包括重定位目标,将来的堆栈和未来的GD位置。
(以下内容仅适用于非SPL版本)
3.设置中间环境,其中堆栈和GD是由board_init_f()在系统RAM中分配的,但BSS和初始化的非常量数据仍然不可用。
4.调用relocate_code()。此功能将U-Boot从其
当前位置到重定位目标中,计算方式为board_init_f()。
5.设置用于调用board_init_r()的最终环境。这个环境有BSS(初始化为0),初始化为非常量数据(已初始化为其预期值),并堆叠在系统中内存。 GD保留了board_init_f()设置的值。一些CPU关于内存,目前还有一些工作要做,所以调用c_runtime_cpu_setup。
6.分支到board_init_r()。

看完注释,已经对这个文件的功能有所了解,我们来跟着代码具体看一下。

ENTRY(_main)/** Set up initial C runtime environment and call board_init_f(0).*/#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)ldr    sp, =(CONFIG_SPL_STACK)
#elseldr    sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endifbic   sp, sp, #7  /* 8-byte alignment for ABI compliance */sub    sp, 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, #0bl    board_init_f

首先将CONFIG_SYS_INIT_SP_ADDR定义的值加载到栈指针sp中,这个宏定义在配置头文件中指定。

这段代码是为board_init_f C函数调用提供环境,也就是栈指针sp初始化。

在endif下面的代码,首先是让sp栈八字节对齐,然后减掉一个GD_SIZE。

这个GD是一个全局结构体,用来保存uboot一些全局信息,所以需要一块单独的内存。

“mov    r9, sp”将sp保存到r9寄存器中,显然,这里r9的值就是GD结构体的首地址。

后面所有code中如果要使用gd结构体,必须在文件中加入DECLARE_GLOBAL_DATA_PTR宏定义,定义如下:

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

继续看,这里使用了命令bl,即跳转到board_init_f函数运行。

四、board_init_f

board_init_f函数本身存在两个地方,uboot/common/board_f.c文件中有,然后uboot/arch/arm/lib/board.c也有,那么到底用哪个呢?

可以告诉大家的是,我的这个项目是用的uboot/arch/arm/lib/board.c中的board_init_f函数。

board_init_c函数也在uboot/arch/arm/lib/board.c中。

void board_init_f(ulong boot_flags)
{……gd->mon_len = (ulong)&__bss_end - (ulong)_start;……for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {if ((*init_fnc_ptr)() != 0) {hang();}}……
}

首先初始化mon_len,这个mon_len代表uboot code的大小。

然后这个for循环,其实这个for循环不停地便利init_sequence这个指针结构体里的函数指针,知道它为NULL。

init_sequence指针结构体内容如下:

init_fnc_t *init_sequence[] = {arch_cpu_init,       /* basic arch cpu dependent setup */mark_bootstage,
#ifdef CONFIG_OF_CONTROLfdtdec_check_fdt,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)board_early_init_f,
#endiftimer_init,       /* initialize timer */
#ifdef CONFIG_BOARD_POSTCLK_INITboard_postclk_init,
#endif
#ifdef CONFIG_FSL_ESDHCget_clocks,
#endifenv_init,     /* initialize environment */init_baudrate,      /* initialze baudrate settings */serial_init,       /* serial communications setup */console_init_f,        /* stage 1 init of console */display_banner,        /* say that we are here */print_cpuinfo,        /* display cpu info (and speed) */
#if defined(CONFIG_DISPLAY_BOARDINFO)checkboard,        /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)init_func_i2c,
#endifdram_init,        /* configure available RAM banks */NULL,
};

arch_cpu_init需要实现,要先启动uboot这里可以先写一个空函数。

如果你只想搞一个精简的uboot,需要做好的就是ddr和serial,所以只需要关心serial_init,console_init_f以及dram_init。

不过因为我的项目需求,需要使用到i2c,所以init_func_i2c也需要多关注一下。

timer_init

初始化定时器,Cortex-A7 内核有一个定时器,这里初始化的就是 Cortex-A 内核的那个定时器。通过这个定时器来为 uboot 提供时间。就跟 Cortex-M 内核 Systick 定时器一样。

serial_init


int serial_init(void)
{return get_current()->start();
}
static struct serial_device *get_current(void)
{struct serial_device *dev;if (!(gd->flags & GD_FLG_RELOC))dev = default_serial_console();else if (!serial_current)dev = default_serial_console();elsedev = serial_current;/* We must have a console device */if (!dev) {
#ifdef CONFIG_SPL_BUILDputs("Cannot find console\n");hang();
#elsepanic("Cannot find console\n");
#endif}return dev;
}

上述两个函数都在uboot/drivers/serial/serial.c中。

通过上述函数,我们可以知道serial_init其实就是调用get_cureent获取到了一个serial,然后调用了这个serial的start函数,执行一些特定的初始化。

接下来,让我们看看get_current具体是怎么获取serial的。

看到第一个if语句,这里的gd->flags还没有初始化,值为0;然后serial_current本身就是我们要获取,所以肯定也是为NULL。显然,这里是通过default_serial_console()函数来获取了一个默认的串口控制台。

console_init_f

设置 gd->have_console 为 1,表示有个控制台,此函数也将前面暂存在缓冲区中的数据通过控制台打印出来。

print_cpuinfo

用于打印 CPU 信息。

init_func_i2c

#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
static int init_func_i2c(void)
{puts("I2C:   ");
#ifdef CONFIG_SYS_I2Ci2c_init_all();
#elsei2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
#endifputs("ready\n");return (0);
}
#endif

可以看到,如果满足条件CONFIG_SYS_I2C,就会调用i2c_init_all函数,如果不满足,则调用i2c_init函数。

这里,调用的是i2c_init_all。

dram_init

对gd->ram_size初始化,以便board_init_f后面代码对dram空间进行规划。

dram_init实现可以通过配置文件定义宏定义来实现,也可以通过对ddrc控制器读获取dram信息。init_sequence结束,又回到board_init_f函数,剩余代码将会对sdram空间进行规划。

所以这里我们就不继续分析了

因此,board_init_f也就结束了,代码又回到_main函数。

五、_main函数第二段

#if ! defined(CONFIG_SPL_BUILD)/** Set up intermediate environment (new sp and gd) and call* relocate_code(addr_moni). Trick here is that we'll return* 'here' but relocated.*/ldr    sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */bic sp, sp, #7  /* 8-byte alignment for ABI compliance */ldr    r9, [r9, #GD_BD]        /* r9 = gd->bd */sub    r9, r9, #GD_SIZE        /* new GD is below bd */adr lr, hereldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off */add lr, lr, r0ldr   r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr */b   relocate_code

前4条汇编实现了新gd结构体的更新。

首先更新sp,并且将sp 8字节对齐,方便后面函数开辟栈能对齐,然后获取gd->bd地址到r9中,需要注意,在board_init_f中gd->bd已经更新为新分配的bd了,下一条汇编将r9减掉bd的size,这样就获取到了board_init_f中新分配的gd了!

后面4条则是为relocate_code做准备,首先加载here地址,然后加上新地址偏移量给lr,则是code relocate后的新here了,relocate_code返回跳转到lr,则是新位置的here!

最后在r0中保存code的新地址,跳转到relocate_code。

六、relocate_code函数

relocate_code函数在arch/arm/lib/relocate.S中,这个函数实现了将uboot code拷贝到gd->relocaddr。

relocate_code函数比较复杂也比较重要,这里我就不试着去解析了,有兴趣的可以去看这个博主的文章,下面是链接。

http://blog.csdn.net/skyflying2012/article/details/37660265。

七、_main函数第三段

relocate_code结束,再次回到_main函数,只是,现在代码已经运行在ram中了。

here:/* Set up final (full) environment */bl c_runtime_cpu_setup /* we still call old routine here */ldr r0, =__bss_start   /* this is auto-relocated! */ldr    r1, =__bss_end     /* this is auto-relocated! */#ifdef CONFIG_UBOOT_BSS_CBDMA_SETsub   r2, r1, r0mov   r1, #0bl    sp_cbdma_memset
#elsemov    r2, #0x00000000     /* prepare zero to clear BSS */clbss_l:cmp  r0, r1          /* while not at end of BSS */strlo  r2, [r0]        /* clear 32-bit BSS word */addlo    r0, r0, #4      /* move to next */blo   clbss_l
#endifbl coloured_LED_initbl red_led_on/* call board_init_r(gd_t *id, ulong dest_addr) */mov    r0, r9          /* gd_t */ldr   r1, [r9, #GD_RELOCADDR] /* dest_addr *//* call board_init_r */ldr   pc, =board_init_r  /* this is auto-relocated! *//* we should not return here. */

第一条汇编直接就跳到了c_runtime_cpu_setup函数。

看到c_runtime_cpu_setup函数。

ENTRY(c_runtime_cpu_setup)
/** If I-cache is enabled invalidate it*/
#ifndef CONFIG_SYS_ICACHE_OFFmcr p15, 0, r0, c7, c5, 0   @ invalidate icachemcr     p15, 0, r0, c7, c10, 4  @ DSB mcr     p15, 0, r0, c7, c5, 4   @ ISB
#endif
/** Move vector table*//* Set vector address in CP15 VBAR register */ldr     r0, =_startmcr     p15, 0, r0, c12, c0, 0  @Set VBARbx  lr  ENDPROC(c_runtime_cpu_setup)

如果icache是enable,则无效掉icache,保证从sdram中更新指令到cache中。

接着更新异常向量表首地址,因为code被relocate,所以异常向量表也被relocate。

从c_runtime_cpu_setup返回,下面一段汇编是将bss段清空。

接下来分别调用了coloured_LED_init以及red_led_on,很多开发板都会有led指示灯,这里可以实现上电指示灯亮,有调试作用。

最后r0赋值gd指针,r1赋值relocaddr,进入最后的board_init_r 。

八、board_init_r函数

void board_init_r(gd_t *id, ulong dest_addr)
{gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */enable_caches();board_init();serial_initialize();#if defined(CONFIG_CMD_NAND)puts("NAND:  ");nand_init();       /* go init the NAND */
#endifif (should_load_env())env_relocate();elseset_default_env(NULL);console_init_r();  /* fully init console as a device *//* set up exceptions */interrupt_init();/* enable exceptions */enable_interrupts();/* main_loop() can return to retry autoboot, if so just run it again. */for (;;) {main_loop();}
}

代码内容我做了省略,如果全部展示出来太多了。函数路径和board_init_f函数路径一样。

首先看到第一段代码,设置gd->flags,告诉其他调用者,代码搬移已经完成。

enable_caches,启用缓存。

board_init,板级初始化。

serial_initialize

重点来说一些serial_initialize,对于最精简能正常启动的uboot,serial和ddr是必须正常工作的。

首先,它和board_init_f函数中的serial_init函数不同,serial_init只是为了获取一个默认的串口调试控制台,用于打印串口信息。

serial_initialize函数是所有串口驱动的初始化集合。

void serial_initialize(void)
{mpc8xx_serial_initialize();ns16550_serial_initialize();pxa_serial_initialize();s3c24xx_serial_initialize();s5p_serial_initialize();mpc512x_serial_initialize();mxs_auart_initialize();arc_serial_initialize();vc0718_serial_initialize();serial_assign(default_serial_console()->name);
}

所有串口驱动都会实现一个xxxx_serial_initialize函数,并且添加到serial_initialize中,xxxx_serial_initialize函数中是将所有需要的串口(用结构体struct serial_device表示,其中实现了基本的收发配置)调用serial_register注册,serial_register如下:

void serial_register(struct serial_device *dev)
{
#ifdef CONFIG_NEEDS_MANUAL_RELOCif (dev->start)dev->start += gd->reloc_off;if (dev->stop)dev->stop += gd->reloc_off;if (dev->setbrg)dev->setbrg += gd->reloc_off;if (dev->getc)dev->getc += gd->reloc_off;if (dev->tstc)dev->tstc += gd->reloc_off;if (dev->putc)dev->putc += gd->reloc_off;if (dev->puts)dev->puts += gd->reloc_off;
#endifdev->next = serial_devices;serial_devices = dev;
}

就是将你的serial_dev加到全局链表serial_devices中。

可以想象,如果你有4个串口,则再你的串口驱动中分别定义4个serial device,并实现对应的收发配置,然后serial_register注册者4个串口。

回到serial-initialize,最后调用serial_assign,default_serial_console我们之前说过,就是你在串口驱动给出一个默认调试串口,serial_assign如下:

int serial_assign(const char *name)
{struct serial_device *s;for (s = serial_devices; s; s = s->next) {if (strcmp(s->name, name))continue;serial_current = s;return 0;}return -EINVAL;
}

serial_assign就是从serial_devices链表中找到指定的默认调试串口,条件就是串口的name,最后serial_current就是当前的默认串口了。

总结一下,serial_initialize工作是将所有serial驱动中所有串口注册到serial_devices链表中,然后找到指定的默认串口。

nand_init,初始化nand flash。

env_relocate,下载环境变量。

console_init_r,完全初始化控制台作为一个设备使用。

interrupt_init,中断初始化。

enable_interrupts,启用中断。

最后,进入死循环,调用main_loop函数。

九、main_loop

main_loop函数在uboot/common/main.c中。

main_loop函数主要就是检测用户输入,是否进入命令行和检测是否要进入uboot升级。

这里我大概介绍一下uboot升级。

我项目的平台式凌阳sunplus,所以在main_loop中有一个名为sp_init_before_boot的函数,在这个函数里面,就会去检测update_flag,如果为1就进入升级模式。

所以,我的usb开发之usb增强ic的i2c驱动也就加到进入升级模式之前,只有初始化好了这个usb的增强ic的i2c,usb才能正常读取u盘数据,完成升级。

U-BOOT学习之2014.4版Uboot启动流程分析相关推荐

  1. 2014.4新版uboot启动流程分析

    原文 http://blog.csdn.net/skyflying2012/article/details/25804209 此处转载有稍作修改 最近开始接触uboot,现在需要将2014.4版本ub ...

  2. 基于IMX6Q的uboot启动流程分析(3):_main函数之relocate_code与board_init_r

    基于IMX6Q的uboot启动流程分析(1):uboot入口函数 基于IMX6Q的uboot启动流程分析(2):_main函数之board_init_f 基于IMX6Q的uboot启动流程分析(3): ...

  3. Exynos4412 Uboot 移植(二)—— Uboot 启动流程分析

    uboot启动流程分析如下: 第一阶段: a -- 设置cpu工作模式为SVC模式 b -- 关闭中断,mmu,cache v -- 关看门狗 d -- 初始化内存,串口 e -- 设置栈 f -- ...

  4. u-boot启动流程分析

    u-boot启动流程分析 以smdk2410为例,分析u-boot的启动流程.u-boot的启动流程是指从cpu上电开机执行u-boot到u-boot成功加载完操作系统的过程.这一过程可以分为两个阶段 ...

  5. linux uboot启动流程分析,uboot启动流程分析

    uboot版本为NXP维护的2016.03版本 下载地址为http://git.freescale.com/git/... 分析uboot的启动流程,需要编译一下uboot,然后打开链接脚本 u-bo ...

  6. am335x uboot启动流程分析

    基本指令含义 .globl _start .globl指示告诉汇编器,_start这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号 b,bl b是不带返回的跳转  bl带返回的跳 ...

  7. u-boot启动流程分析(1)_平台相关部分

    转自:http://www.wowotech.net/u-boot/boot_flow_1.html 1. 前言 本文将结合u-boot的"board->machine->arc ...

  8. Uboot 启动流程分析

    uboot启动流程 复位CPU 设置异常向量表 设置cpu为SVC模式 但是从U-Boot方面考虑,其要做的事情是初始化系统的相关硬件资源,因此需要获取尽量多的权限,以方便操作硬件,初始化硬件. 关闭 ...

  9. Uboot启动流程分析

    Uboot是嵌入式系统中最常用的bootloader,这里我们以s3c2410为例分析一下uboot的启动流程.首先通过uboot的链接文件,我们可以看到uboot运行是执行的第一段代码在start. ...

最新文章

  1. 中国博士生提出最先进AI训练优化器,收敛快精度高,网友亲测:Adam可以退休了...
  2. 缩小sql server 日志文件
  3. 深入分析 Java I/O 的工作机制
  4. 今年双 11,阿里业务 100% 上云,云原生有哪些技术亮点?
  5. Java中实现根据一个List中的数据的两个属性相同划分为同一类
  6. java 双等于 equals_在Java中等于equals vs Arrays.equals
  7. 51nod 1050 循环数组最大子段和【环形DP/最大子段和/正难则反】
  8. Beta阶段第四次Scrum Meeting
  9. linux apache 配置视频教程,《Linux服务器配置视频教程》ubuntu centos apache iptables 后盾网向军老师主讲[WMV]...
  10. 记录一次网线水晶头的安装
  11. 搭建内网BT服务器(转)
  12. 我国正式实施不安全食品召回制度(转)
  13. Java创建SpringBoot服务时yml文件显示粉色不显示绿色小叶子
  14. Android 获取蓝牙设备类型
  15. Docker:从入门到入门
  16. python支持复数以及相关的运算吗_python怎么实现复数运算
  17. nag在逆向中是什么意思_OD调试4----去除nag窗口的几种方法
  18. itutorgroup:在线教育与传统线下教育的七大区别你知多少?
  19. php体育网站模板,运动健身企业的网站模板
  20. 重磅!2019「FAT」90位90后杰出从业者榜单揭晓

热门文章

  1. 【每日一词】churlish
  2. python绘图:利用matlibplot绘制雷达图
  3. 微信开发踩坑系列一之Native支付
  4. android 清空arp缓存表,ARP缓存表 相关命令 arp-a /arp-d
  5. 威洛特:狗狗哮喘会出现哪些症状,如何去有效预防?
  6. MEMS惯性器件选型笔记
  7. 从0到1,Vue大牛的前端搭建——异常监控系统
  8. 校园网中锐捷客户端与VMware的NAT转换冲突问题解决
  9. Android性能分析之emmc坏块测试
  10. 计算机基础知识速记,计算机二级公共基础知识速记.pdf