本帖最后由 正点原子01 于 2019-12-25 15:23 编辑
1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址::https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
4)对正点原子Linux感兴趣的同学可以加群讨论:935446741
5)关注正点原子公众号,获取最新资料更新
第三十二章U-Boot启动流程详解
上一章我们详细的分析了uboot的顶层Makefile,理清了uboot的编译流程。本章我们来详细的分析一下uboot的启动流程,理清uboot是如何启动的。通过对uboot启动流程的梳理,我们就可以掌握一些外设是在哪里被初始化的,这样当我们需要修改这些外设驱动的时候就会心里有数。另外,通过分析uboot的启动流程可以了解Linux内核是如何被启动的。
32.1 链接脚本u-boot.lds详解 要分析uboot的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口。如果没有编译过uboot的话链接脚本为arch/arm/cpu/u-boot.lds。但是这个不是最终使用的链接脚本,最终的链接脚本是在这个链接脚本的基础上生成的。编译一下uboot,编译完成以后就会在uboot根目录下生成u-boot.lds文件,如图32.1.1所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.gif 图32.1.1 链接脚本 只有编译u-boot以后才会在根目录下出现u-boot.lds文件! 只有编译u-boot以后才会在根目录下出现u-boot.lds文件! 只有编译u-boot以后才会在根目录下出现u-boot.lds文件! 打开u-boot.lds,内容如下: 示例代码32.1.1 u-boot.lds文件代码 - OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm")
- OUTPUT_ARCH(arm)
- ENTRY(_start)
- SECTIONS
- {
- .=0x00000000;
- .= ALIGN(4);
- .text :
- {
- *(.__image_copy_start)
- *(.vectors)
- arch/arm/cpu/armv7/start.o (.text*)
- *(.text*)
- }
- .= ALIGN(4);
- .rodata :{*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))}
- .= ALIGN(4);
- .data :{
- *(.data*)
- }
- .= ALIGN(4);
- .=.;
- .= ALIGN(4);
- .u_boot_list :{
- KEEP(*(SORT(.u_boot_list*)));
- }
- .= ALIGN(4);
- .image_copy_end :
- {
- *(.__image_copy_end)
- }
- .rel_dyn_start :
- {
- *(.__rel_dyn_start)
- }
- .rel.dyn :{
- *(.rel*)
- }
- .rel_dyn_end :
- {
- *(.__rel_dyn_end)
- }
- .end :
- {
- *(.__end)
- }
- _image_binary_end =.;
- .= ALIGN(4096);
- .mmutable :{
- *(.mmutable)
- }
- .bss_start __rel_dyn_start (OVERLAY):{
- KEEP(*(.__bss_start));
- __bss_base =.;
- }
- .bss __bss_base (OVERLAY):{
- *(.bss*)
- .= ALIGN(4);
- __bss_limit =.;
- }
- .bss_end __bss_limit (OVERLAY):{
- KEEP(*(.__bss_end));
- }
- .dynsym _image_binary_end :{*(.dynsym)}
- .dynbss :{*(.dynbss)}
- .dynstr :{*(.dynstr*)}
- .dynamic :{*(.dynamic*)}
- .plt :{*(.plt*)}
- .interp :{*(.interp*)}
- .gnu.hash :{*(.gnu.hash)}
- .gnu :{*(.gnu*)}
- .ARM.exidx :{*(.ARM.exidx*)}
- .gnu.linkonce.armexidx :{*(.gnu.linkonce.armexidx.*)}
- }
复制代码 第3行为代码当前入口点:_start, _start在文件arch/arm/lib/vectors.S中有定义,如图32.1.2所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg 图32.1.2 _start入口 从图32.1.1可以看出,_start后面就是中断向量表,从图中的“.section".vectors", "ax”可以得到,此代码存放在.vectors段里面。 第10行,使用如下命令在uboot中查找“__image_copy_start”: - grep -nR "__image_copy_start"
复制代码搜索结果如图32.1.3所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image006.gif 图32.1.3 查找结果 打开u-boot.map,找到如图32.1.4所示位置:
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image008.jpg 图32.1.4 u-boot.map u-boot.map是uboot的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,从图32.1.4的932行可以看到__image_copy_start为0X87800000,而.text的起始地址也是0X87800000。 第11行是vectors段,vectors段保存中断向量表,从图32.1.2中我们知道了vectors.S的代码是存在vectors段中的。从图32.1.4可以看出,vectors段的起始地址也是0X87800000,说明整个uboot的起始地址就是0X87800000,这也是为什么我们裸机例程的链接起始地址选择0X87800000了,目的就是为了和uboot一致。 第12行将arch/arm/cpu/armv7/start.s编译出来的代码放到中断向量表后面。 第13行为text段,其他的代码段就放到这里 在u-boot.lds中有一些跟地址有关的“变量”需要我们注意一下,后面分析u-boot源码的时候会用到,这些变量要最终编译完成才能确定的!!!比如我编译完成以后这些“变量”的值如表32.1.1所示: 表32.1.1 uboot相关变量表 表32.1.1中的“变量”值可以在u-boot.map文件中查找,表32.1.1中除了__image_copy_start以外,其他的变量值每次编译的时候可能会变化,如果修改了uboot代码、修改了uboot配置、选用不同的优化等级等等都会影响到这些值。所以,一切以实际值为准! 32.2 U-Boot启动流程详解32.2.1reset函数源码详解从u-boot.lds中我们已经知道了入口点是arch/arm/lib/vectors.S文件中的_start,代码如下: 示例代码32.2.1.1 vectors.S代码段 - /*
- *************************************************************
- *
- * Exception vectors as described in ARM reference manuals
- *
- * Uses indirect branch to allow reaching handlers anywhere in
- * memory.
- **************************************************************
- */
- _start:
- #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
- .word CONFIG_SYS_DV_NOR_BOOT_CFG
- #endif
- b reset
- ldr pc, _undefined_instruction
- ldr pc, _software_interrupt
- ldr pc, _prefetch_abort
- ldr pc, _data_abort
- ldr pc, _not_used
- ldr pc, _irq
- ldr pc, _fiq
复制代码第48行_start开始的是中断向量表,其中54~61行就是中断向量表,和我们裸机例程里面一样。54行跳转到reset函数里面,reset函数在arch/arm/cpu/armv7/start.S里面,代码如下: 示例代码32.2.1.2 start.S代码段 - /*****************************************************************
- *
- * Startup Code (reset vector)
- *
- * Do important init only if we don't startfrom memory!
- * Setup memory and board specific bits priorto relocation.
- * Relocate armboot to ram. Setup stack.
- *
- *****************************************************************/
- .globl reset
- .globl save_boot_params_ret
- reset:
- /* Allow theboard to save important registers */
- b save_boot_params
复制代码 第35行就是reset函数。 第37行从reset函数跳转到了save_boot_params函数,而save_boot_params函数同样定义在start.S里面,定义如下: 示例代码32.2.1.3 start.S代码段 - /******************************************************************
- *
- * void save_boot_params(u32 r0, u32 r1, u32r2, u32 r3)
- * __attribute__((weak));
- *
- * Stack pointer is not yet initialized atthis moment
- * Don't save anything to stack even ifcompiled with -O0
- *
- ******************************************************************/
- ENTRY(save_boot_params)
- b save_boot_params_ret @ backto my caller
复制代码 save_boot_params函数也是只有一句跳转语句,跳转到save_boot_params_ret函数,save_boot_params_ret函数代码如下: 示例代码32.2.1.4 start.S代码段 - save_boot_params_ret:
- /*
- * disable interrupts (FIQ and IRQ), also setthe cpu to SVC32
- * mode, except if in HYP mode already
- */
- mrs r0, cpsr
- and r1, r0, #0x1f @ mask mode bits
- teq r1, #0x1a @ test for HYP mode
- bicne r0, r0, #0x1f @ clear all modebits
- orrne r0, r0, #0x13 @ set SVC mode
- orr r0, r0, #0xc0 @ disable FIQ andIRQ
- msr cpsr,r0
复制代码 第43行,读取寄存器cpsr中的值,并保存到r0寄存器中。 第44行,将寄存器r0中的值与0X1F进行与运算,结果保存到r1寄存器中,目的就是提取cpsr的bit0~bit4这5位,这5位为M4 M3 M2 M1M0,M[4:0]这五位用来设置处理器的工作模式,如表32.2.1.1所示: 第45行,判断r1寄存器的值是否等于0X1A(0b11010),也就是判断当前处理器模式是否处于Hyp模式。 第46行,如果r1和0X1A不相等,也就是CPU不处于Hyp模式的话就将r0寄存器的bit0~5进行清零,其实就是清除模式位 第47行,如果处理器不处于Hyp模式的话就将r0的寄存器的值与0x13进行或运算,0x13=0b10011,也就是设置处理器进入SVC模式。 第48行,r0寄存器的值再与0xC0进行或运算,那么r0寄存器此时的值就是0xD3,cpsr的I为和F位分别控制IRQ和FIQ这两个中断的开关,设置为1就关闭了FIQ和IRQ! 第49行,将r0寄存器写回到cpsr寄存器中。完成设置CPU处于SVC32模式,并且关闭FIQ和IRQ这两个中断。 继续执行执行下面的代码: 示例代码32.2.1.5 start.S代码段 - /*
- * Setup vector:
- * (OMAP4 spl TEXT_BASE is not 32 bytealigned.
- * Continue to use ROM code vector only inOMAP4 spl)
- */
- #if!(defined(CONFIG_OMAP44XX)&& defined(CONFIG_SPL_BUILD))
- /* Set V=0 in CP15SCTLR register - for VBAR to point to vector */
- mrc p15,0, r0, c1, c0,0 @ Read CP15 SCTLR Register
- bic r0, #CR_V @ V =0
- mcr p15,0, r0, c1, c0,0 @ Write CP15 SCTLR Register
- /* Set vector addressin CP15 VBAR register */
- ldr r0,=_start
- mcr p15,0, r0, c12, c0,0 @Set VBAR
- #endif
复制代码第56行,如果没有定义CONFIG_OMAP44XX和CONFIG_SPL_BUILD的话条件成立,此处条件成立。 第58行读取CP15中c1寄存器的值到r0寄存器中,根据17.1.4小节可知,这里是读取SCTLR寄存器的值。 第59行,CR_V在arch/arm/include/asm/system.h中有如下所示定义: #define CR_V (1<< 13) /* Vectors relocated to0xffff0000 */ 因此这一行的目的就是清除SCTLR寄存器中的bit13,SCTLR寄存器结构如图32.2.1.1所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image010.jpg 图32.2.1.1 SCTLR寄存器结构图 从图32.2.1.1可以看出,bit13为V位,此位是向量表控制位,当为0的时候向量表基地址为0X00000000,软件可以重定位向量表。为1的时候向量表基地址为0XFFFF0000,软件不能重定位向量表。这里将V清零,目的就是为了接下来的向量表重定位,这个我们在第十七章有过详细的介绍了。 第60行将r0寄存器的值重写写入到寄存器SCTLR中。 第63行设置r0寄存器的值为_start,_start就是整个uboot的入口地址,其值为0X87800000,相当于uboot的起始地址,因此0x87800000也是向量表的起始地址。 第64行将r0寄存器的值(向量表值)写入到CP15的c12寄存器中,也就是VBAR寄存器。因此第58~64行就是设置向量表重定位的。 代码继续往下执行: 示例代码32.2.1.6 start.S代码段 - /* the mask ROMcode should have PLL and others stable */
- #ifndefCONFIG_SKIP_LOWLEVEL_INIT
- bl cpu_init_cp15
- bl cpu_init_crit
- #endif
- bl _main
复制代码第68行如果没有定义CONFIG_SKIP_LOWLEVEL_INIT的话条件成立。我们没有定义CONFIG_SKIP_LOWLEVEL_INIT,因此条件成立,执行下面的语句。 示例代码32.2.1.6中的内容比较简单,就是分别调用函数cpu_init_cp15、cpu_init_crit和_main。 函数cpu_init_cp15用来设置CP15相关的内容,比如关闭MMU啥的,此函数同样在start.S文件中定义的,代码如下: 示例代码32.2.1.7 start.S代码段 - /*****************************************************************
- *
- * 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 MCR
- mcr p15,0, r0, c8, c7,0 @ invalidate TLBs
- mcr p15,0, r0, c7, c5,0 @ invalidate icache
- mcr p15,0, r0, c7, c5,6 @ invalidate BP array
- mcr p15,0, r0, c7, c10,4 @ DSB
- mcr p15,0, r0, c7, c5,4 @ ISB
- /*
- * 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 11(Z---) BTB
- #ifdefCONFIG_SYS_ICACHE_OFF
- bic r0, r0, #0x00001000 @ clear bit 12(I) I-cache
- #else
- orr r0, r0, #0x00001000 @ set bit 12(I) I-cache
- #endif
- mcr p15,0, r0, c1, c0,0
- ......
- mov pc, r5 @ back to my caller
- ENDPROC(cpu_init_cp15)
复制代码 函数cpu_init_cp15都是一些和CP15有关的内容,我们不用关心,有兴趣的可以详细的看一下。 函数cpu_init_crit也在是定义在start.S文件中,函数内容如下: 示例代码32.2.1.8 start.S代码段 - /*****************************************************************
- *
- * CPU_init_critical registers
- *
- * setup important registers
- * setup memory timing
- *
- *****************************************************************/
- ENTRY(cpu_init_crit)
- /*
- * Jump to board specificinitialization...
- * The Mask ROM will have alreadyinitialized
- * basic memory. Go here to bump up clockrate and handle
- * wake up conditions.
- */
- b lowlevel_init @ go setup pll,mux,memory
- ENDPROC(cpu_init_crit)
复制代码 可以看出函数cpu_init_crit内部仅仅是调用了函数lowlevel_init,接下来就是详细的分析一下lowlevel_init和_main这两个函数。 32.2.2 lowlevel_init函数详解函数lowlevel_init在文件arch/arm/cpu/armv7/lowlevel_init.S中定义,内容如下: 示例代码32.2.2.1 lowlevel_init.S代码段 - #include <asm-offsets.h>
- #include <config.h>
- #include <linux/linkage.h>
- ENTRY(lowlevel_init)
- /*
- * Setup a temporary stack. Global data is not available yet.
- */
- ldr sp,=CONFIG_SYS_INIT_SP_ADDR
- bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- #ifdef CONFIG_SPL_DM
- mov r9, #0
- #else
- /*
- * Set up global data for boards that still need it. This will be
- * removed soon.
- */
- #ifdef CONFIG_SPL_BUILD
- ldr r9,=gdata
- #else
- sub sp, sp, #GD_SIZE
- bic sp, sp, #7
- mov r9, sp
- #endif
- #endif
- /*
- * Save the old lr(passed in ip) and the current lr to stack
- */
- push {ip, lr}
- /*
- * Call the very early init function. This should do only the
- * absolute bare minimum to get started. It should not:
- *
- * - set up DRAM
- * - use global_data
- * - clear BSS
- * - try to start a console
- *
- * For boards with SPL this should be empty since SPL can do all
- * of this init in the SPL board_init_f() function which is
- * called immediately after this.
- */
- bl s_init
- pop {ip, pc}
- ENDPROC(lowlevel_init)
复制代码第22行设置sp指向CONFIG_SYS_INIT_SP_ADDR,CONFIG_SYS_INIT_SP_ADDR在include/configs/mx6ullevk.h文件中,在mx6ullevk.h中有如下所示定义: 示例代码32.2.2.2 mx6ullevk.h代码段 - #defineCONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR
- #defineCONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE
- #defineCONFIG_SYS_INIT_SP_OFFSET \
- (CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
- #defineCONFIG_SYS_INIT_SP_ADDR \
- (CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)
复制代码 示例代码32.2.2.2中的IRAM_BASE_ADDR和IRAM_SIZE在文件arch/arm/include/asm/arch-mx6/imx-regs.h中有定义,如下所示,其实就是IMX6UL/IM6ULL内部ocram的首地址和大小。 示例代码32.2.2.3 imx-regs.h代码段 - #define IRAM_BASE_ADDR 0x00900000
- ...
- #if!(defined(CONFIG_MX6SX)|| defined(CONFIG_MX6UL)|| \
- defined(CONFIG_MX6SLL)|| defined(CONFIG_MX6SL))
- #defineIRAM_SIZE 0x00040000
- #else
- #defineIRAM_SIZE 0x00020000
- #endif
复制代码 如果408行的条件成立的话IRAM_SIZE=0X40000,当定义了CONFIG_MX6SX、CONFIG_MX6U、CONFIG_MX6SLL和CONFIG_MX6SL中的任意一个的话条件就不成立,在.config中定义了CONFIG_MX6UL,所以条件不成立,因此IRAM_SIZE=0X20000=128KB。 结合示例代码32.2.2.2,可以得到如下值: - CONFIG_SYS_INIT_RAM_ADDR = IRAM_BASE_ADDR = 0x00900000。
- CONFIG_SYS_INIT_RAM_SIZE = 0x00020000 =128KB。
复制代码还需要知道GENERATED_GBL_DATA_SIZE的值,在文件include/generated/generic-asm-offsets.h中有定义,如下: 示例代码32.2.2.4generic-asm-offsets.h代码段
- #ifndef __GENERIC_ASM_OFFSETS_H__
- #define __GENERIC_ASM_OFFSETS_H__
- /*
- * DO NOT MODIFY.
- *
- * This file was generated by Kbuild
- */
- #define GENERATED_GBL_DATA_SIZE 256
- #defineGENERATED_BD_INFO_SIZE 80
- #define GD_SIZE 248
- #define GD_BD 0
- #defineGD_MALLOC_BASE 192
- #defineGD_RELOCADDR 48
- #defineGD_RELOC_OFF 68
- #defineGD_START_ADDR_SP 64
- #endif
复制代码
GENERATED_GBL_DATA_SIZE=256,GENERATED_GBL_DATA_SIZE的含义为(sizeof(struct global_data) + 15) & ~15 。 综上所述,CONFIG_SYS_INIT_SP_ADDR值如下: - CONFIG_SYS_INIT_SP_OFFSET = 0x00020000 –256=0x1FF00。
- CONFIG_SYS_INIT_SP_ADDR = 0x00900000 +0X1FF00 = 0X0091FF00,
复制代码结果如下图所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image012.gif 图32.2.2.1 sp值 此时sp指向0X91FF00,这属于IMX6UL/IMX6ULL的内部ram。 继续回到文件lowlevel_init.S,第23行对sp指针做8字节对齐处理! 第34行,sp指针减去GD_SIZE,GD_SIZE同样在generic-asm-offsets.h中定了,大小为248,见示例代码32.2.2.4第11行。 第35行对sp做8字节对齐,此时sp的地址为0X0091FF00-248=0X0091FE08,此时sp位置如图32.2.2.2所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image014.gif 图32.2.2.2 sp值 第36行将sp地址保存在r9寄存器中。 第42行将ip和lr压栈 第57行调用函数s_init,得,又来了一个函数。 第58行将第36行入栈的ip和lr进行出栈,并将lr赋给pc。 32.2.3 s_init函数详解在上一小节中,我们知道lowlevel_init函数后面会调用s_init函数,s_init函数定义在文件arch/arm/cpu/armv7/mx6/soc.c中,如下所示: 示例代码32.2.3.1 soc.c代码段 - void s_init(void)
- {
- struct anatop_regs *anatop =(struct anatop_regs *)ANATOP_BASE_ADDR;
- struct mxc_ccm_reg *ccm =(struct mxc_ccm_reg *)CCM_BASE_ADDR;
- u32 mask480;
- u32 mask528;
- u32 reg, periph1, periph2;
- if(is_cpu_type(MXC_CPU_MX6SX)|| is_cpu_type(MXC_CPU_MX6UL)||
- is_cpu_type(MXC_CPU_MX6ULL)|| is_cpu_type(MXC_CPU_MX6SLL))
- return;
- /* Due to hardwarelimitation, on MX6Q we need to gate/ungate
- * all PFDs to make sure PFD is workingright, otherwise, PFDs
- * may not output clock after reset, MX6DLand MX6SL have added
- * 396M pfd workaround in ROM code, as busclock need it
- */
- mask480 = ANATOP_PFD_CLKGATE_MASK(0)|
- ANATOP_PFD_CLKGATE_MASK(1)|
- ANATOP_PFD_CLKGATE_MASK(2)|
- ANATOP_PFD_CLKGATE_MASK(3);
- mask528 = ANATOP_PFD_CLKGATE_MASK(1)|
- ANATOP_PFD_CLKGATE_MASK(3);
- reg = readl(&ccm->cbcmr);
- periph2 =((reg & MXC_CCM_CBCMR_PRE_PERIPH2_CLK_SEL_MASK)
- >> MXC_CCM_CBCMR_PRE_PERIPH2_CLK_SEL_OFFSET);
- periph1 =((reg & MXC_CCM_CBCMR_PRE_PERIPH_CLK_SEL_MASK)
- >> MXC_CCM_CBCMR_PRE_PERIPH_CLK_SEL_OFFSET);
- /* Checking if PLL2PFD0 or PLL2 PFD2 is using for periph clock */
- if((periph2 !=0x2)&&(periph1 !=0x2))
- mask528 |= ANATOP_PFD_CLKGATE_MASK(0);
- if((periph2 !=0x1)&&(periph1 !=0x1)&&
- (periph2 !=0x3)&&(periph1 !=0x3))
- mask528 |= ANATOP_PFD_CLKGATE_MASK(2);
- writel(mask480,&anatop->pfd_480_set);
- writel(mask528,&anatop->pfd_528_set);
- writel(mask480,&anatop->pfd_480_clr);
- writel(mask528,&anatop->pfd_528_clr);
- }
复制代码在第816行会判断当前CPU类型,如果CPU为MX6SX、MX6UL、MX6ULL或MX6SLL中的任意一种,那么就会直接返回,相当于s_init函数什么都没做。所以对于I.MX6UL/I.MX6ULL来说,s_init就是个空函数。从s_init函数退出以后进入函数lowlevel_init,但是lowlevel_init函数也执行完成了,返回到了函数cpu_init_crit,函数cpu_init_crit也执行完成了,最终返回到save_boot_params_ret,函数调用路径如图32.2.3.1所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image016.gif 图32.2.3.1 uboot函数调用路径 从图32.2.3.1可知,接下来要执行的是save_boot_params_ret中的_main函数,接下来分析_main函数。 32.2.4_main函数详解_main函数定义在文件arch/arm/lib/crt0.S中,函数内容如下: 示例代码32.2.4.1 crt0.S代码段 - /*
- * entry point of crt0 sequence
- */
- ENTRY(_main)
- /*
- * Set up initial C runtime environment andcall board_init_f(0).
- */
- #if defined(CONFIG_SPL_BUILD)&& defined(CONFIG_SPL_STACK)
- ldr sp,=(CONFIG_SPL_STACK)
- #else
- ldr sp,=(CONFIG_SYS_INIT_SP_ADDR)
- #endif
- #if defined(CONFIG_CPU_V7M)/* v7M forbids using SP as BIC destination */
- mov r3, sp
- bic r3, r3, #7
- mov sp, r3
- #else
- bic sp, sp, #7/* 8-byte alignment for ABI compliance */
- #endif
- mov r0, sp
- bl board_init_f_alloc_reserve
- mov sp, r0
- /* set up gd here,outside any C code */
- mov r9, r0
- bl board_init_f_init_reserve
- mov r0, #0
- bl board_init_f
- #if! defined(CONFIG_SPL_BUILD)
- /*
- * Set up intermediate environment (new spand gd) and call
- * relocate_code(addr_moni). Trick here isthat we'll return
- * 'here' but relocated.
- */
- ldr sp,[r9, #GD_START_ADDR_SP]/* sp = gd->start_addr_sp */
- #if defined(CONFIG_CPU_V7M)/* v7M forbidsusing SP as BIC destination */
- mov r3, sp
- bic r3, r3, #7
- mov sp, r3
- #else
- bic sp, sp, #7/* 8-byte alignment for ABI compliance */
- #endif
- ldr r9,[r9, #GD_BD]/* r9 = gd->bd */
- sub r9, r9, #GD_SIZE /* new GD is below bd */
- adr lr, here
- ldr r0,[r9, #GD_RELOC_OFF]/* r0 = gd->reloc_off */
- add lr, lr, r0
- #if defined(CONFIG_CPU_V7M)
- orr lr, #1/* As required by Thumb-only */
- #endif
- ldr r0,[r9, #GD_RELOCADDR]/* r0 = gd->relocaddr */
- b relocate_code
- here:
- /*
- * now relocate vectors
- */
- bl relocate_vectors
- /* Set up final(full) environment */
- bl c_runtime_cpu_setup /* we still callold routine here */
- #endif
- #if!defined(CONFIG_SPL_BUILD)|| defined(CONFIG_SPL_FRAMEWORK)
- # ifdefCONFIG_SPL_BUILD
- /* Use a DRAM stackfor the rest of SPL, if requested */
- bl spl_relocate_stack_gd
- cmp r0, #0
- movne sp, r0
- movne r9, r0
- # endif
- ldr r0,=__bss_start /* this is auto-relocated! */
- #ifdefCONFIG_USE_ARCH_MEMSET
- ldr r3,=__bss_end /* this is auto-relocated! */
- mov r1, #0x00000000/* prepare zero to clear BSS */
- subs r2, r3, r0 /* r2 =memset len */
- bl memset
- #else
- ldr r1,=__bss_end /* this is auto-relocated! */
- mov r2, #0x00000000/* prepare zero to clear BSS */
- clbss_l:cmp r0, r1 /* while not at end of BSS */
- #if defined(CONFIG_CPU_V7M)
- itt lo
- #endif
- strlo r2,[r0]/* clear32-bit BSS word */
- addlo r0, r0, #4/* move to next */
- blo clbss_l
- #endif
- #if! defined(CONFIG_SPL_BUILD)
- bl coloured_LED_init
- bl red_led_on
- #endif
- /* callboard_init_r(gd_t *id, ulong dest_addr) */
- mov r0, r9 /* gd_t */
- ldr r1,[r9, #GD_RELOCADDR]/* dest_addr */
- /* call board_init_r*/
- #if defined(CONFIG_SYS_THUMB_BUILD)
- ldr lr,=board_init_r /* this is auto-relocated! */
- bx lr
- #else
- ldr pc,=board_init_r /* this is auto-relocated! */
- #endif
- /* we should notreturn here. */
- #endif
- ENDPROC(_main)
复制代码第76行,设置sp指针为CONFIG_SYS_INIT_SP_ADDR,也就是sp指向0X0091FF00。 第83行,sp做8字节对齐。 第85行,读取sp到寄存器r0里面,此时r0=0X0091FF00。 第86行,调用函数board_init_f_alloc_reserve,此函数有一个参数,参数为r0中的值,也就是0X0091FF00,此函数定义在文件common/init/board_init.c中,内容如下: 示例代码32.2.4.2 board_init.c代码段 - ulongboard_init_f_alloc_reserve(ulong top)
- {
- /* Reserve earlymalloc arena */
- #if defined(CONFIG_SYS_MALLOC_F)
- top -= CONFIG_SYS_MALLOC_F_LEN;
- #endif
- /* LAST : reserveGD (rounded up to a multiple of 16 bytes) */
- top = rounddown(top-sizeof(struct global_data),16);
- return top;
- }
复制代码函数board_init_f_alloc_reserve主要是留出早期的malloc内存区域和gd内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400(在文件include/generated/autoconf.h中定义),sizeof(struct global_data)=248(GD_SIZE值),完成以后的内存分布如图32.2.4.1所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image018.gif 图32.2.4.1 内存分布图 函数board_init_f_alloc_reserve是有返回值的,返回值为新的top值,从图32.2.4.1可知,此时top=0X0091FA00。 继续回到示例代码32.2.4.1中,第87行,将r0写入到sp里面,r0保存着函数board_init_f_alloc_reserve的返回值,所以这一句也就是设置sp=0X0091FA00。 第89行,将r0寄存器的值写到寄存器r9里面,因为r9寄存器存放着全局变量gd的地址,在文件arch/arm/include/asm/global_data.h中有如图32.2.4.2所示宏定义: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image020.jpg 图32.2.4.2 DECLARE_GLOBAL_DATA_PTR宏定义 从图32.2.4.2可以看出,uboot中定义了一个指向gd_t的指针gd,gd存放在寄存器r9里面的,因此gd是个全局变量。gd_t是个结构体,在include/asm-generic/global_data.h里面有定义,gd_定义如下: 示例代码32.2.4.3 global_data.h代码段 - typedefstruct global_data {
- bd_t *bd;
- unsignedlong flags;
- unsignedint baudrate;
- unsignedlong cpu_clk;/* CPU clock in Hz! */
- unsignedlong bus_clk;
- /* We cannotbracket this with CONFIG_PCI due to mpc5xxx */
- unsignedlong pci_clk;
- unsignedlong mem_clk;
- #if defined(CONFIG_LCD)|| defined(CONFIG_VIDEO)
- unsignedlong fb_base;/* Base address of framebuffer mem */
- #endif
- ......
- #ifdefCONFIG_DM_VIDEO
- ulong video_top;/* Top of video frame buffer area */
- ulong video_bottom;/* Bottom of videoframe buffer area */
- #endif
- } gd_t;
复制代码因此这一行代码就是设置gd所指向的位置,也就是gd指向0X0091FA00。 继续回到示例代码32.2.4.1中,第90行调用函数board_init_f_init_reserve,此函数在文件common/init/board_init.c中有定义,函数内容如下: 示例代码32.2.4.4 board_init.c代码段 - void board_init_f_init_reserve(ulong base)
- {
- struct global_data *gd_ptr;
- #ifndef_USE_MEMCPY
- int*ptr;
- #endif
- /*
- * clear GD entirely and set it up.
- * Use gd_ptr, as gd may not be properlyset yet.
- */
- gd_ptr =(struct global_data *)base;
- /* zero the area */
- #ifdef _USE_MEMCPY
- memset(gd_ptr,'\0',sizeof(*gd));
- #else
- for(ptr =(int*)gd_ptr; ptr <(int*)(gd_ptr +1);)
- *ptr++=0;
- #endif
- /* set GD unlessarchitecture did it already */
- #if!defined(CONFIG_ARM)
- arch_setup_gd(gd_ptr);
- #endif
- /* next alloc willbe higher by one GD plus 16-byte alignment */
- base += roundup(sizeof(struct global_data),16);
- /*
- * record early malloc arena start.
- * Use gd as it is now properly set forall architectures.
- */
- #if defined(CONFIG_SYS_MALLOC_F)
- /* go down one'early malloc arena' */
- gd->malloc_base = base;
- /* next alloc willbe higher by one 'early malloc arena' size */
- base += CONFIG_SYS_MALLOC_F_LEN;
- #endif
- }
复制代码可以看出,此函数用于初始化gd,其实就是清零处理。另外,此函数还设置了gd->malloc_base为gd基地址+gd大小=0X0091FA00+248=0X0091FAF8,在做16字节对齐,最终gd->malloc_base=0X0091FB00,这个也就是earlymalloc的起始地址。 继续回到示例代码32.2.4.1中,第92行设置R0为0。 第93行,调用board_init_f函数,此函数定义在文件common/board_f.c中!主要用来初始化DDR,定时器,完成代码拷贝等等,此函数我们后面在详细的分析。 第103行,重新设置环境(sp和gd)、获取gd->start_addr_sp的值赋给sp,在函数board_init_f中会初始化gd的所有成员变量,其中gd->start_addr_sp=0X9EF44E90,所以这里相当于设置sp=gd->start_addr_sp=0X9EF44E90。0X9EF44E90是DDR中的地址,说明新的sp和gd将会存放到DDR中,而不是内部的RAM了。GD_START_ADDR_SP=64,参考示例代码32.2.2.4。 第109行,sp做8字节对齐。 第111行,获取gd->bd的地址赋给r9,此时r9存放的是老的gd,这里通过获取gd->bd的地址来计算出新的gd的位置。GD_BD=0,参考示例代码32.2.2.4。 第112行,新的gd在bd下面,所以r9减去gd的大小就是新的gd的位置,获取到新的gd的位置以后赋值给r9。 第114行,设置lr寄存器为here,这样后面执行其他函数返回的时候就返回到了第122行的here位置处。 第115,读取gd->reloc_off的值复制给r0寄存器,GD_RELOC_OFF=68,参考示例代码32.2.2.4。 第116行,lr寄存器的值加上r0寄存器的值,重新赋值给lr寄存器。因为接下来要重定位代码,也就是把代码拷贝到新的地方去(现在的uboot存放的起始地址为0X87800000,下面要将uboot拷贝到DDR最后面的地址空间出,将0X87800000开始的内存空出来),其中就包括here,因此lr中的here要使用重定位后的位置。 第120行,读取gd->relocaddr的值赋给r0寄存器,此时r0寄存器就保存着uboot要拷贝的目的地址,为0X9FF47000。GD_RELOCADDR=48,参考示例代码32.2.2.4。 第121行,调用函数relocate_code,也就是代码重定位函数,此函数负责将uboot拷贝到新的地方去,此函数定义在文件arch/arm/lib/relocate.S中稍后会详细分析此函数。 第127行,调用函数relocate_vectors,对中断向量表做重定位,此函数定义在文件arch/arm/lib/relocate.S中,稍后会详细分析此函数。 第131行,调用函数c_runtime_cpu_setup,此函数定义在文件arch/arm/cpu/armv7/start.S中,函数内容如下: 示例代码32.2.4.5 start.S代码段 - ENTRY(c_runtime_cpu_setup)
- /*
- * If I-cache is enabled invalidate it
- */
- #ifndefCONFIG_SYS_ICACHE_OFF
- mcr p15,0, r0, c7, c5,0 @ invalidate icache
- mcr p15,0, r0, c7, c10,4 @ DSB
- mcr p15,0, r0, c7, c5,4 @ ISB
- #endif
- bx lr
- ENDPROC(c_runtime_cpu_setup)
复制代码第141~159行,清除BSS段。 第167行,设置函数board_init_r的两个参数,函数board_init_r声明如下: - board_init_r(gd_t *id, ulong dest_addr)
复制代码第一个参数是gd,因此读取r9保存到r0里面。 第168行,设置函数board_init_r的第二个参数是目的地址,因此r1=gd->relocaddr。 第174行、调用函数board_init_r,此函数定义在文件common/board_r.c中,稍后会详细的分析此函数。 这个就是_main函数的运行流程,在_main函数里面调用了board_init_f、relocate_code、relocate_vectors和board_init_r这4个函数,接下来依次看一下这4个函数都是干啥的。 32.2.5 board_init_f函数详解_main中会board_init_f函数,board_init_f函数主要有两个工作: ①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。 ②、初始化gd的各个成员变量,uboot会将自己重定位到DRAM最后面的地址区域,也就是将自己拷贝到DRAM最后面的内存区域中。这么做的目的是给Linux腾出空间,防止Linuxkernel覆盖掉uboot,将DRAM前面的区域完整的空出来。在拷贝之前肯定要给uboot各部分分配好内存位置和大小,比如gd应该存放到哪个位置,malloc内存池应该存放到哪个位置等等。这些信息都保存在gd的成员变量中,因此要对gd的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位uboot的时候就会用到这个内存“分配图”。 此函数定义在文件common/board_f.c中定义,代码如下: 示例代码32.2.5.1 board_f.c代码段 - void board_init_f(ulong boot_flags)
- {
- #ifdefCONFIG_SYS_GENERIC_GLOBAL_DATA
- /*
- * For some archtectures, global data isinitialized and used
- * before calling this function. The data should be preserved.
- * For others,CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined
- * and use the stack here to host globaldata until relocation.
- */
- gd_t data;
- gd =&data;
- /*
- * Clear global data before it is accessedat debug print
- * in initcall_run_list. Otherwise thedebug print probably
- * get the wrong vaule ofgd->have_console.
- */
- zero_global_data();
- #endif
- gd->flags = boot_flags;
- gd->have_console =0;
- if(initcall_run_list(init_sequence_f))
- hang();
- #if!defined(CONFIG_ARM)&&!defined(CONFIG_SANDBOX)&& \
- !defined(CONFIG_EFI_APP)
- /* NOTREACHED -jump_to_copy() does not return */
- hang();
- #endif
- }
复制代码 因为没有定义CONFIG_SYS_GENERIC_GLOBAL_DATA,所以第1037~1054行代码无效。 第1056行,初始化gd->flags=boot_flags=0。 第1057行,设置gd->have_console=0。 重点在第1059行!通过函数initcall_run_list来运行初始化序列init_sequence_f里面的一些列函数,init_sequence_f里面包含了一系列的初始化函数,init_sequence_f也是定义在文件common/board_f.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的init_sequence_f定义如下: 示例代码32.2.5.1 board_f.c代码段 - /*****************去掉条件编译语句后的init_sequence_f***************/
- static init_fnc_t init_sequence_f[]={
- setup_mon_len,
- initf_malloc,
- initf_console_record,
- arch_cpu_init, /* basic arch cpu dependent setup */
- initf_dm,
- arch_cpu_init_dm,
- mark_bootstage, /* need timer, go after init dm */
- board_early_init_f,
- timer_init, /* initialize timer */
- board_postclk_init,
- get_clocks,
- env_init, /* initialize environment */
- init_baud_rate, /* initialze baudrate settings */
- serial_init, /* serial communications setup */
- console_init_f, /* stage 1 init of console */
- display_options, /* say that we are here */
- display_text_info, /* show debugging info if required */
- print_cpuinfo, /* display cpu info (and speed) */
- show_board_info,
- INIT_FUNC_WATCHDOG_INIT
- INIT_FUNC_WATCHDOG_RESET
- init_func_i2c,
- announce_dram_init,
- /* TODO: unify allthese dram functions? */
- dram_init, /* configure available RAM banks */
- post_init_f,
- INIT_FUNC_WATCHDOG_RESET
- testdram,
- INIT_FUNC_WATCHDOG_RESET
- INIT_FUNC_WATCHDOG_RESET
- /*
- * Now that we have DRAM mapped and working, wecan
- * relocate the code and continue running fromDRAM.
- *
- * Reserve memory at end of RAM for (top downin that order):
- * -area that won't get touched by U-Boot and Linux (optional)
- * -kernel log buffer
- * -protected RAM
- * - LCDframebuffer
- * -monitor code
- * -board info struct
- */
- setup_dest_addr,
- reserve_round_4k,
- reserve_mmu,
- reserve_trace,
- reserve_uboot,
- reserve_malloc,
- reserve_board,
- setup_machine,
- reserve_global_data,
- reserve_fdt,
- reserve_arch,
- reserve_stacks,
- setup_dram_config,
- show_dram_config,
- display_new_sp,
- INIT_FUNC_WATCHDOG_RESET
- reloc_fdt,
- setup_reloc,
- NULL,
- };
复制代码接下来分析以上函数执行完以后的结果: 第2行,setup_mon_len函数设置gd的mon_len成员变量,此处为__bss_end -_start,也就是整个代码的长度。0X878A8E74-0x87800000=0XA8E74,这个就是代码长度 第3行,initf_malloc函数初始化gd中跟malloc有关的成员变量,比如malloc_limit,此函数会设置gd->malloc_limit =CONFIG_SYS_MALLOC_F_LEN=0X400。malloc_limit表示malloc内存池大小。 第4行,initf_console_record,如果定义了宏CONFIG_CONSOLE_RECORD和宏CONFIG_SYS_MALLOC_F_LEN的话此函数就会调用函数console_record_init,但是IMX6ULL的uboot没有定义宏CONFIG_CONSOLE_RECORD,所以此函数直接返回0。 第5行,arch_cpu_init函数,初始化架构相关的内容,CPU级别的操作。 第6行,initf_dm函数,驱动模型的一些初始化。 第7行,arch_cpu_init_dm函数未实现。 第8行,mark_bootstage函数应该是和啥标记有关的 第9行,board_early_init_f函数,板子相关的早期的一些初始化设置,I.MX6ULL用来初始化串口的IO配置 第10行,timer_init,初始化定时器,Cortex-A7内核有一个定时器,这里初始化的就是Cortex-A内核的那个定时器。通过这个定时器来为uboot提供时间。就跟Cortex-M内核Systick定时器一样。关于Cortex-A内部定时器的详细内容,请参考文档《ARM ArchitectureReference Manual ARMv7-A andARMv7-R edition.pdf》的“Chapter B8 The Generic Timer”章节。 第11行,board_postclk_init,对于I.MX6ULL来说是设置VDDSOC电压。 第12行,get_clocks函数用于获取一些时钟值,I.MX6ULL获取的是sdhc_clk时钟,也就是SD卡外设的时钟。 第13行,env_init函数是和环境变量有关的,设置gd的成员变量env_addr,也就是环境变量的保存地址。 第14行,init_baud_rate函数用于初始化波特率,根据环境变量baudrate来初始化gd->baudrate。 第15行,serial_init,初始化串口。 第16行,console_init_f,设置gd->have_console 为1,表示有个控制台,此函数也将前面暂存在缓冲区中的数据通过控制台打印出来。 第17行、display_options,通过串口输出一些信息,如图32.2.5.1所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image022.jpg 图32.2.5.1 串口信息输出 第18行,display_text_info,打印一些文本信息,如果开启UBOOT的DEBUG功能的话就会输出text_base、bss_start、bss_end,形式如下: debug("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",text_base,bss_start, bss_end); 结果如图32.2.5.2所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image024.jpg 图32.2.5.2 文本信息 第19行,print_cpuinfo函数用于打印CPU信息,结果如图32.2.5.3所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image026.jpg 图32.2.5.3 CPU信息 第20行,show_board_info函数用于打印板子信息,会调用checkboard函数,结果如图32.2.5.4所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image028.jpg 图32.2.5.4 板子信息 第21行,INIT_FUNC_WATCHDOG_INIT,初始化看门狗,对于I.MX6ULL来说是空函数 第22行,INIT_FUNC_WATCHDOG_RESET,复位看门狗,对于I.MX6ULL来说是空函数 第23行,init_func_i2c函数用于初始化I2C,初始化完成以后会输出如图32.2.5.5所示信息: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image030.jpg 图32.2.5.5 I2C初始化信息输出 第24行,announce_dram_init,此函数很简单,就是输出字符串“DRAM:” 第26行,dram_init,并非真正的初始化DDR,只是设置gd->ram_size的值,对于正点原子I.MX6ULL开发板EMMC版本核心板来说就是512MB。 第27行,post_init_f,此函数用来完成一些测试,初始化gd->post_init_f_time 第29行,testdram,测试DRAM,空函数。 第44行,setup_dest_addr函数,设置目的地址,设置gd->ram_size,gd->ram_top,gd->relocaddr这三个的值。接下来我们会遇到很多跟数值有关的设置,如果直接看代码分析的话就太费时间了,我可以修改uboot代码,直接将这些值通过串口打印出来,比如这里我们修改文件common/board_f.c,因为setup_dest_addr函数定义在文件common/board_f.c中,在setup_dest_addr函数输入如图32.2.5.6所示内容: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image032.gif 图32.2.5.6 添加print函数打印成员变量值 设置好以后重新编译uboot,然后烧写到SD卡中,选择SD卡启动,重启开发板,打开SecureCRT,uboot会输出如图32.2.5.7所示信息: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image034.jpg 图32.2.5.7 信息输出 从图32.2.5.7可以看出: - gd->ram_size = 0X20000000 //ram大小为0X20000000=512MB
- gd->ram_top = 0XA0000000 //ram最高地址为0X80000000+0X20000000=0XA0000000
- gd->relocaddr = 0XA0000000 //重定位后最高地址为0XA0000000
复制代码第45行,reserve_round_4k函数用于对 gd->relocaddr做4KB对齐,因为gd->relocaddr=0XA0000000,已经是4K对齐了,所以调整后不变。 第46行,reserve_mmu,留出MMU的TLB表的位置,分配MMU的TLB表内存以后会对gd->relocaddr做64K字节对齐。完成以后gd->arch.tlb_size、gd->arch.tlb_addr和gd->relocaddr如图32.2.5.8所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image036.jpg 图32.2.5.8 信息输出 从图32.2.5.8可以看出: - gd->arch.tlb_size= 0X4000 //MMU的TLB表大小
- gd->arch.tlb_addr=0X9FFF0000 //MMU的TLB表起始地址,64KB对齐以后
- gd->relocaddr=0X9FFF0000 //relocaddr地址
复制代码第47行,reserve_trace函数,留出跟踪调试的内存,I.MX6ULL没有用到! 第48行,reserve_uboot,留出重定位后的uboot所占用的内存区域,uboot所占用大小由gd->mon_len所指定,留出uboot的空间以后还要对gd->relocaddr做4K字节对齐,并且重新设置gd->start_addr_sp,结果如图32.2.5.9所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image038.jpg 图32.2.5.9 信息输出 从图32.2.5.9可以看出: - gd->mon_len = 0XA8EF4
- gd->start_addr_sp = 0X9FF47000
- gd->relocaddr = 0X9FF47000
复制代码第49行,reserve_malloc,留出malloc区域,调整gd->start_addr_sp位置,malloc区域由宏TOTAL_MALLOC_LEN定义,宏定义如下: - #define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)
复制代码mx6ull_alientek_emmc.h文件中定义宏CONFIG_SYS_MALLOC_LEN为16MB=0X1000000,宏CONFIG_ENV_SIZE=8KB=0X2000,因此TOTAL_MALLOC_LEN=0X1002000。调整以后gd->start_addr_sp如图32.2.5.10所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image040.jpg 图32.2.5.10 信息输出 从图32.2.5.10可以看出: - TOTAL_MALLOC_LEN=0X1002000
- gd->start_addr_sp=0X9EF45000 //0X9FF47000-16MB-8KB=0X9EF45000
复制代码第50行,reserve_board函数,留出板子bd所占的内存区,bd是结构体bd_t,bd_t大小为80字节,结果如图32.2.5.11所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image042.jpg 图32.2.5.10 信息输出 从图32.2.5.11可以看出: - gd->start_addr_sp=0X9EF44FB0
- gd->bd=0X9EF44FB0
复制代码第51行,setup_machine,设置机器ID,linux启动的时候会和这个机器ID匹配,如果匹配的话linux就会启动正常。但是!!I.MX6ULL不用这种方式了,这是以前老版本的uboot和linux使用的,新版本使用设备树了,因此此函数无效。 第52行,reserve_global_data函数,保留出gd_t的内存区域,gd_t结构体大小为248B,结果如图32.2.5.11所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image044.jpg 图32.2.5.11 信息输出 - gd->start_addr_sp=0X9EF44EB8 //0X9EF44FB0-248=0X9EF44EB8
- gd->new_gd=0X9EF44EB8
复制代码第53行,reserve_fdt,留出设备树相关的内存区域,I.MX6ULL的uboot没有用到,因此此函数无效。 第54行,reserve_arch是个空函数。 第55行,reserve_stacks,留出栈空间,先对gd->start_addr_sp减去16,然后做16字节对其。如果使能IRQ的话还要留出IRQ相应的内存,具体工作是由arch/arm/lib/stack.c文件中的函数arch_reserve_stacks完成。结果如图32.2.5.12所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image046.jpg 图32.2.5.12 信息输出 在本uboot中并没有使用到IRQ,所以不会留出IRQ相应的内存区域,此时: - gd->start_addr_sp=0X9EF44E90
复制代码第56行,setup_dram_config函数设置dram信息,就是设置gd->bd->bi_dram[0].start和gd->bd->bi_dram[0].size,后面会传递给linux内核,告诉linux DRAM的起始地址和大小。结果如图32.2.5.13所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image048.jpg 图32.2.5.13 信息输出 从图32.2.5.13可以看出,DRAM的起始地址为0X80000000,大小为0X20000000(512MB)。 第57行,show_dram_config函数,用于显示DRAM的配置,如图32.2.5.14所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image050.jpg 图32.2.5.14 信息输出 第58行,display_new_sp函数,显示新的sp位置,也就是gd->start_addr_sp,不过要定义宏DEBUG,结果如图32.2.5.15所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image052.jpg 图32.2.5.15 信息输出 图32.2.5.15中的gd->start_addr_sp值和我们前面分析的最后一次修改的值一致。 第60行,reloc_fdt函数用于重定位fdt,没有用到。 第61行,setup_reloc,设置gd的其他一些成员变量,供后面重定位的时候使用,并且将以前的gd拷贝到gd->new_gd处。需要使能DEBUG才能看到相应的信息输出,如图32.2.5.16所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image054.jpg 图32.2.5.16 信息输出 从图32.2.5.16可以看出,uboot重定位后的偏移为0X18747000,重定位后的新地址为0X9FF4700,新的gd首地址为0X9EF44EB8,最终的sp为0X9EF44E90。 至此,board_init_f函数就执行完成了,最终的内存分配如图32.2.5.16所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image056.gif 图32.2.5.16 最终的内存分配图 32.2.6 relocate_code函数详解relocate_code函数是用于代码拷贝的,此函数定义在文件arch/arm/lib/relocate.S中,代码如下: 示例代码32.2.6.1 relocate.S代码段 - /*
- * voidrelocate_code(addr_moni)
- *
- * This functionrelocates the monitor code.
- *
- * NOTE:
- * To prevent thecode below from containing references with an
- * R_ARM_ABS32relocation record type, we never refer to linker-
- * definedsymbols directly. Instead, we declare literals which
- * contain theirrelative location with respect to relocate_code,
- * and at run time, add relocate_code back to them.
- */
- ENTRY(relocate_code)
- ldr r1,=__image_copy_start /* r1 <- SRC &__image_copy_start */
- subs r4, r0, r1 /* r4 <- relocation offset */
- beq relocate_done /* skip relocation */
- ldr r2,=__image_copy_end /* r2 <- SRC &__image_copy_end */
- copy_loop:
- ldmia r1!,{r10-r11} /* copy from sourceaddress [r1] */
- stmia r0!,{r10-r11}/* copy to target address [r0] */
- cmp r1, r2 /* until source end address [r2] */
- blo copy_loop
- /*
- * fix .rel.dyn relocations
- */
- ldr r2,=__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
- ldr r3,=__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
- fixloop:
- ldmia r2!,{r0-r1} /* (r0,r1)<- (SRC location,fixup) */
- and r1, r1, #0xff
- cmp r1, #23 /* relative fixup? */
- bne fixnext
- /* relative fix:increase location by offset */
- add r0, r0, r4
- ldr r1,[r0]
- add r1, r1, r4
- str r1,[r0]
- fixnext:
- cmp r2, r3
- blo fixloop
- relocate_done:
- #ifdef __XSCALE__
- /*
- * On xscale, icache must be invalidatedand write buffers
- * drained, even with cache disabled -4.2.7 of xscale core
- developer's manual */
- mcr p15,0, r0, c7, c7,0 /* invalidateicache */
- mcr p15,0, r0, c7, c10,4 /* drain writebuffer */
- #endif
- /* ARMv4- don'tknow bx lr but the assembler fails to see that */
- #ifdef__ARM_ARCH_4__
- mov pc, lr
- #else
- bx lr
- #endif
- ENDPROC(relocate_code)
复制代码第80行,r1=__image_copy_start,也就是r1寄存器保存源地址,由表31.4.1.1可知,__image_copy_start=0X87800000。 第81行, r0=0X9FF47000,这个地址就是uboot拷贝的目标首地址。r4=r0-r1=0X9FF47000-0X87800000=0X18747000,因此r4保存偏移量。 第82行,如果在第81中,r0-r1等于0,说明r0和r1相等,也就是源地址和目的地址是一样的,那肯定就不需要拷贝了!执行relocate_done函数 第83行,r2=__image_copy_end,r2中保存拷贝之前的代码结束地址,由表31.4.1.1可知,__image_copy_end =0x8785dd54。 第84行,函数copy_loop完成代码拷贝工作!从r1,也就是__image_copy_start开始,读取uboot代码保存到r10和r11中,一次就只拷贝这2个32位的数据。拷贝完成以后r1的值会更新,保存下一个要拷贝的数据地址。 第87行,将r10和r11的数据写到r0开始的地方,也就是目的地址。写完以后r0的值会更新,更新为下一个要写入的数据地址。 第88行,比较r1是否和r2相等,也就是检查是否拷贝完成,如果不相等的话说明没有拷贝完成,没有拷贝完成的话就跳转到copy_loop接着拷贝,直至拷贝完成。 接下来的第94行~109行是重定位.rel.dyn段,.rel.dyn段是存放.text段中需要重定位地址的集合。重定位就是uboot将自身拷贝到DRAM的另一个地放去继续运行(DRAM的高地址处)。我们知道,一个可执行的bin文件,其链接地址和运行地址要相等,也就是链接到哪个地址,在运行之前就要拷贝到哪个地址去。现在我们重定位以后,运行地址就和链接地址不同了,这样寻址的时候不会出问题吗?为了分析这个问题,我们需要在mx6ull_alientek_emmc.c中输入如下所示内容: 示例代码32.2.6.2 mx6ull_alientek_emmc.c新添代码段 - staticint rel_a =0;
- void rel_test(void)
- {
- rel_a =100;
- printf("rel_test\r\n");
- }
复制代码 最后还需要在mx6ullevk.c文件中的board_init函数里面调用rel_test函数,否则rel_reset不会被编译进uboot。修改完成后的mx6ullevk.c如图32.2.6.1所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image058.jpg 图32.2.6.1 加入rel测试相关代码 board_init函数会调用rel_test,rel_test会调用全局变量rel_a,使用如下命令编译uboot: - ./mx6ull_alientek_emmc.sh
复制代码编译完成以后,使用arm-linux-gnueabihf-objdump将u-boot进行反汇编,得到u-boot.dis这个汇编文件,命令如下: - arm-linux-gnueabihf-objdump -D -m arm u-boot >u-boot.dis
复制代码 在u-boot.dis文件中找到rel_a、rel_rest和board_init,相关内容如下所示: 示例代码32.2.6.3 汇编文件代码段 - 87804184<rel_test>:
- 87804184: e59f300c ldr r3,[pc, #12] ;87804198<rel_test+0x14>
- 87804188: e3a02064 mov r2, #100 ;0x64
- 8780418c: e59f0008 ldr r0,[pc, #8] ;8780419c<rel_test+0x18>
- 87804190: e5832000 str r2,[r3]
- 87804194: ea00d668 b 87839b3c<printf>
- 87804198:8785da50;<UNDEFINED> instruction:0x8785da50
- 8780419c:878426a2 strhi r2,[r4, r2, lsr #13]
- 878041a0<board_init>:
- 878041a0: e92d4010 push {r4, lr}
- 878041a4: ebfffff6 bl 87804184<rel_test>
- ......
- 8785da50<rel_a>:
- 8785da50:00000000 andeq r0, r0, r0
复制代码 第12行是borad_init调用rel_test函数,用到了bl指令,而bl指令时位置无关指令,bl指令是相对寻址的(pc+offset),因此uboot中函数调用是与绝对位置无关的。 再来看一下函数rel_test对于全局变量rel_a的调用,第2行设置r3的值为pc+12地址处的值,因为ARM流水线的原因,pc寄存器的值为当前地址+8,因此pc=0X87804184+8=0X8780418C,r3=0X8780418C+12=0X87804198,第7行就是0X87804198这个地址,0X87804198处的值为0X8785DA50。根据第17行可知,0X8785DA50正是变量rel_a的地址,最终r3=0X8785DA50。 第3行,r2=100。 第5行,将r2内的值写到r3地址处,也就是设置地址0X8785DA50的值为100,这不就是示例代码代码32.2.6.2中的第5行:rel_a = 100。 总结一下rel_a=100的汇编执行过程: ①、在函数rel_test末尾处有一个地址为0X87804198的内存空间(示例代码32.2.6.3第7行),此内存空间保存着变量rel_a的地址。 ②、函数rel_test要想访问变量rel_a,首先访问末尾的0X87804198来获取变量rel_a的地址,而访问0X87804198是通过偏移来访问的,很明显是个位置无关的操作。 ③、通过0X87804198获取到变量rel_a的地址,对变量rel_a进行操作。 ④、可以看出,函数rel_test对变量rel_a的访问没有直接进行,而是使用了一个第三方偏移地址0X87804198,专业术语叫做Label。这个第三方偏移地址就是实现重定位后运行不会出错的重要原因! uboot重定位后偏移为0X18747000,那么重定位后函数rel_test的首地址就是0X87804184+0X18747000=0X9FF4B184。保存变量rel_a地址的Label就是0X9FF4B184+8+12=0X9FF4B198(既:0X87804198+0X18747000),变量rel_a的地址就为0X8785DA50+0X18747000=0X9FFA4A50。重定位后函数rel_test要想正常访问变量rel_a就得设置0X9FF4B198(重定位后的Label)地址出的值为0X9FFA4A50(重定位后的变量rel_a地址)。这样就解决了重定位后链接地址和运行地址不一致的问题。 可以看出,uboot对于重定位后链接地址和运行地址不一致的解决方法就是采用位置无关码,在使用ld进行链接的时候使用选项“-pie”生成位置无关的可执行文件。在文件arch/arm/config.mk下有如下代码: 示例代码32.2.6.4 config.mk文件代码段 - # needed for relocation
- LDFLAGS_u-boot +=-pie
复制代码 第83行就是设置uboot链接选项,加入了“-pie”选项,编译链接uboot的时候就会使用到“-pie”,如图32.2.6.2所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image060.gif 图32.2.6.2 链接命令 使用“-pie”选项以后会生成一个.rel.dyn段,uboot就是靠这个.rel.dyn来解决重定位问题的,在u-bot.dis的.rel.dyn段中有如下所示内容: 示例代码32.2.6.5 .rel.dyn段代码段 - Disassembly ofsection .rel.dyn:
- 8785da44<__rel_dyn_end-0x8ba0>:
- 8785da44:87800020 strhi r0,[r0, r0, lsr #32]
- 8785da48:00000017 andeq r0, r0, r7, lsl r0
- ......
- 785dfb4:87804198;<UNDEFINED> instruction:0x87804198
- 785dfb8:00000017 andeq r0, r0, r7, lsl r0
复制代码先来看一下.rel.dyn段的格式,类似第7行和第8行这样的是一组,也就是两个4字节数据为一组。高4字节是Label地址标识0X17,低4字节就是Label的地址,首先判断Label地址标识是否正确,也就是判断高4字节是否为0X17,如果是的话高4字节就是Label值。 第7行值为0X87804198,第8行为0X00000017,说明第7行的0X87804198是个Label,这个正是示例代码32.2.6.3中存放变量rel_a地址的那个Label。根据前面的分析,只要将地址0X87804198+offset处的值改为重定位后的变量rel_a地址即可。我们猜测的是否正确,看一下uboot对.rel.dyn段的重定位即可(示例代码代码32.2.6.1中的第94~109行),.rel.dyn段的重定位代码如下: 示例代码32.2.6.6 relocate.S代码段 - /*
- * fix .rel.dyn relocations
- */
- ldr r2,=__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
- ldr r3,=__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
- fixloop:
- ldmia r2!,{r0-r1} /* (r0,r1)<- (SRC location,fixup) */
- and r1, r1, #0xff
- cmp r1, #23 /* relative fixup? */
- bne fixnext
- /* relative fix:increase location by offset */
- add r0, r0, r4
- ldr r1,[r0]
- add r1, r1, r4
- str r1,[r0]
- fixnext:
- cmp r2, r3
- blo fixloop
复制代码第94行,r2=__rel_dyn_start,也就是.rel.dyn段的起始地址。 第95行,r3=__rel_dyn_end,也就是.rel.dyn段的终止地址。 第97行,从.rel.dyn段起始地址开始,每次读取两个4字节的数据存放到r0和r1寄存器中,r0存放低4字节的数据,也就是Label地址;r1存放高4字节的数据,也就是Label标志。 第98行,r1中给的值与0xff进行与运算,其实就是取r1的低8位。 第99行,判断r1中的值是否等于23(0X17)。 第100行,如果r1不等于23的话就说明不是描述Label的,执行函数fixnext,否则的话继续执行下面的代码。 第103行,r0保存着Label值,r4保存着重定位后的地址偏移,r0+r4就得到了重定位后的Label值。此时r0保存着重定位后的Label值,相当于0X87804198+0X18747000=0X9FF4B198。 第104,读取重定位后Label所保存的变量地址,此时这个变量地址还是重定位前的(相当于rel_a重定位前的地址0X8785DA50),将得到的值放到r1寄存器中。 第105行,r1+r4即可得到重定位后的变量地址,相当于rel_a重定位后的0X8785DA50+0X18747000=0X9FFA4A50。 第106行,重定位后的变量地址写入到重定位后的Label中,相等于设置地址0X9FF4B198处的值为0X9FFA4A50。 第108行,比较r2和r3,查看.rel.dyn段重定位是否完成。 第109行,如果r2和r3不相等,说明.rel.dyn重定位还未完成,因此跳到fixloop继续重定位.rel.dyn段。 可以看出,uboot中对.rel.dyn段的重定位方法和我们猜想的一致。.rel.dyn段的重定位比较复杂一点,有点绕,因为涉及到链接地址和运行地址的问题。 32.2.7 relocate_vectors函数详解函数relocate_vectors用于重定位向量表,此函数定义在文件函数源码如下: 示例代码32.2.7.1 relocate.S代码段 - ENTRY(relocate_vectors)
- #ifdefCONFIG_CPU_V7M
- /*
- * On ARMv7-M we only have to write the newvector address
- * to VTOR register.
- */
- ldr r0,[r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- ldr r1,=V7M_SCB_BASE
- str r0,[r1, V7M_SCB_VTOR]
- #else
- #ifdefCONFIG_HAS_VBAR
- /*
- * If the ARM processor has the securityextensions,
- * use VBAR to relocate the exception vectors.
- */
- ldr r0,[r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- mcr p15,0, r0, c12, c0,0 /* Set VBAR */
- #else
- /*
- * Copy the relocated exception vectors to the
- * correct address
- * CP15 c1 V bit gives us the location of thevectors:
- * 0x00000000 or 0xFFFF0000.
- */
- ldr r0,[r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- mrc p15,0, r2, c1, c0,0 /* V bit (bit[13])in CP15 c1 */
- ands r2, r2, #(1<<13)
- ldreq r1,=0x00000000 /* If V=0 */
- ldrne r1,=0xFFFF0000 /* If V=1 */
- ldmia r0!,{r2-r8,r10}
- stmia r1!,{r2-r8,r10}
- ldmia r0!,{r2-r8,r10}
- stmia r1!,{r2-r8,r10}
- #endif
- #endif
- bx lr
- ENDPROC(relocate_vectors)
复制代码第29行,如果定义了CONFIG_CPU_V7M的话就执行第30~36行的代码,这是Cortex-M内核单片机执行的语句,因此对于I.MX6ULL来说是无效的。 第38行,如果定义了CONFIG_HAS_VBAR的话就执行此语句,这个是向量表偏移,Cortex-A7是支持向量表偏移的。而且,在.config里面定义了CONFIG_HAS_VBAR,因此会执行这个分支。 第43行,r0=gd->relocaddr,也就是重定位后uboot的首地址,向量表肯定是从这个地址开始存放的。 第44行,将r0的值写入到CP15的VBAR寄存器中,也就是将新的向量表首地址写入到寄存器VBAR中,设置向量表偏移。 32.2.8 board_init_r函数详解第32.2.5小节讲解了board_init_f函数,在此函数里面会调用一系列的函数来初始化一些外设和gd的成员变量。但是board_init_f并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数board_init_r来完成的,board_init_r函数定义在文件common/board_r.c中,代码如下: 示例代码32.2.8.1 board_r.c代码段 - void board_init_r(gd_t *new_gd, ulong dest_addr)
- {
- #ifdef CONFIG_NEEDS_MANUAL_RELOC
- int i;
- #endif
- #ifdef CONFIG_AVR32
- mmu_init_r(dest_addr);
- #endif
- #if!defined(CONFIG_X86)&&!defined(CONFIG_ARM)&&!defined(CONFIG_ARM64)
- gd = new_gd;
- #endif
- #ifdefCONFIG_NEEDS_MANUAL_RELOC
- for(i =0; i < ARRAY_SIZE(init_sequence_r); i++)
- init_sequence_r[i]+= gd->reloc_off;
- #endif
- if(initcall_run_list(init_sequence_r))
- hang();
- /* NOTREACHED -run_main_loop() does not return */
- hang();
- }
复制代码第1010行调用initcall_run_list函数来执行初始化序列init_sequence_r,init_sequence_r是一个函数集合,init_sequence_r也定义在文件common/board_r.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的init_sequence_r定义如下: 示例代码32.2.8.2 board_r.c代码段 - init_fnc_t init_sequence_r[]={
- initr_trace,
- initr_reloc,
- initr_caches,
- initr_reloc_global_data,
- initr_barrier,
- initr_malloc,
- initr_console_record,
- bootstage_relocate,
- initr_bootstage,
- board_init,/* Setup chipselects */
- stdio_init_tables,
- initr_serial,
- initr_announce,
- INIT_FUNC_WATCHDOG_RESET
- INIT_FUNC_WATCHDOG_RESET
- INIT_FUNC_WATCHDOG_RESET
- power_init_board,
- initr_flash,
- INIT_FUNC_WATCHDOG_RESET
- initr_nand,
- initr_mmc,
- initr_env,
- INIT_FUNC_WATCHDOG_RESET
- initr_secondary_cpu,
- INIT_FUNC_WATCHDOG_RESET
- stdio_add_devices,
- initr_jumptable,
- console_init_r,/* fully init console as a device */
- INIT_FUNC_WATCHDOG_RESET
- interrupt_init,
- initr_enable_interrupts,
- initr_ethaddr,
- board_late_init,
- INIT_FUNC_WATCHDOG_RESET
- INIT_FUNC_WATCHDOG_RESET
- INIT_FUNC_WATCHDOG_RESET
- initr_net,
- INIT_FUNC_WATCHDOG_RESET
- run_main_loop,
- };
复制代码第2行,initr_trace函数,如果定义了宏CONFIG_TRACE的话就会调用函数trace_init,初始化和调试跟踪有关的内容。 第3行,initr_reloc函数用于设置gd->flags,标记重定位完成。 第4行,initr_caches函数用于初始化cache,使能cache。 第5行,initr_reloc_global_data函数,初始化重定位后gd的一些成员变量。 第6行,initr_barrier函数,I.MX6ULL未用到。 第7行,initr_malloc函数,初始化malloc。 第8行,initr_console_record函数,初始化控制台相关的内容,I.MX6ULL未用到,空函数。 第9行,bootstage_relocate函数,启动状态重定位。 第10行,initr_bootstage函数,初始化bootstage什么的。 第11行,board_init函数,板级初始化,包括74XX芯片,I2C、FEC、USB和QSPI等。这里执行的是mx6ull_alientek_emmc.c文件中的board_init函数。 第12行,stdio_init_tables函数,stdio相关初始化。 第13行,initr_serial函数,初始化串口。 第14行,initr_announce函数,与调试有关,通知已经在RAM中运行。 第18行,power_init_board函数,初始化电源芯片,正点原子的I.MX6ULL开发板没有用到。 第19行,initr_flash函数,对于I.MX6ULL而言,没有定义宏CONFIG_SYS_NO_FLASH的话函数initr_flash才有效。但是mx6_common.h中定义了宏CONFIG_SYS_NO_FLASH,所以此函数无效。 第21行,initr_nand函数,初始化NAND,如果使用NAND版本核心板的话就会初始化NAND。 第22行,initr_mmc函数,初始化EMMC,如果使用EMMC版本核心板的话就会初始化EMMC,串口输出如图32.2.8.1所示信息: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image062.jpg 图32.2.8.1 EMMC信息输出 从图32.2.8.1可以看出,此时有两个EMCM设备,FSL_SDHC:0和FSL_SDHC:1。 第23行,initr_env函数,初始化环境变量。 第25行,initr_secondary_cpu函数,初始化其他CPU核,I.MX6ULL只有一个核,因此此函数没用。 第27行,stdio_add_devices函数,各种输入输出设备的初始化,如LCD driver,I.MX6ULL使用drv_video_init函数初始化LCD。会输出如图32.2.8.2所示信息: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image064.jpg 图32.2.8.2 LCD信息 第28行,initr_jumptable函数,初始化跳转表。 第29行,console_init_r函数,控制台初始化,初始化完成以后此函数会调用stdio_print_current_devices函数来打印出当前的控制台设备,如图32.2.8.3所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image066.jpg 图32.2.8.3 控制台信息 第31行,interrupt_init函数,初始化中断。 第32行,initr_enable_interrupts函数,使能中断。 第33行,initr_ethaddr函数,初始化网络地址,也就是获取MAC地址。读取环境变量“ethaddr”的值。 第34行,board_late_init函数,板子后续初始化,此函数定义在文件mx6ull_alientek_emmc.c中,如果环境变量存储在EMMC或者SD卡中的话此函数会调用board_late_mmc_env_init函数初始化EMMC/SD。会切换到正在时候用的emmc设备,代码如图32.2.8.4所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image068.jpg 图32.2.8.4 board_late_mmc_env_init函数 图32.2.8.4中的第46行和第47行就是运行“mmcdevxx”命令,用于切换到正在使用的EMMC设备,串口输出信息如图32.2.8.5所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image070.jpg 图32.2.8.5 切换mmc设备 第38行,initr_net函数,初始化网络设备,函数调用顺序为:initr_net->eth_initialize->board_eth_init(),串口输出如图32.2.8.6所示信息: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image072.jpg 图32.2.8.6 网络信息输出 第40行,run_main_loop行,主循环,处理命令。 32.2.9 run_main_loop函数详解 uboot启动以后会进入3秒倒计时,如果在3秒倒计时结束之前按下按下回车键,那么就会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动Linux内核,这个功能就是由run_main_loop函数来完成的。run_main_loop函数定义在文件common/board_r.c中,函数内容如下: 示例代码32.2.9.1 board_r.c文件代码段 - staticint run_main_loop(void)
- {
- #ifdef CONFIG_SANDBOX
- sandbox_main_loop_init();
- #endif
- /* main_loop() canreturn to retry autoboot, if so just run it again */
- for(;;)
- main_loop();
- return0;
- }
复制代码 第7行和第8行是个死循环,“for(;;)”和“while(1)”功能一样,死循环里面就一个main_loop函数,main_loop函数定义在文件common/main.c里面,代码如下: 示例代码32.2.9.2 main.c文件代码段 - /* We come hereafter U-Boot is initialised and ready to process commands */
- void main_loop(void)
- {
- constchar*s;
- bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP,"main_loop");
- #ifndefCONFIG_SYS_GENERIC_BOARD
- puts("Warning: Your board does not use genericboard. Please read\n");
- puts("doc/README.generic-board and takeaction. Boards not\n");
- puts("upgraded by the late 2014 may break orbe removed.\n");
- #endif
- #ifdefCONFIG_VERSION_VARIABLE
- setenv("ver", version_string);/* set version variable */
- #endif /* CONFIG_VERSION_VARIABLE */
- cli_init();
- run_preboot_environment_command();
- #if defined(CONFIG_UPDATE_TFTP)
- update_tftp(0UL,NULL,NULL);
- #endif /* CONFIG_UPDATE_TFTP */
- s = bootdelay_process();
- if(cli_process_fdt(&s))
- cli_secure_boot_cmd(s);
- autoboot_command(s);
- cli_loop();
- }
复制代码 第48行,调用bootstage_mark_name函数,打印出启动进度。 第57行,如果定义了宏CONFIG_VERSION_VARIABLE的话就会执行函数setenv,设置换将变量ver的值为version_string,也就是设置版本号环境变量。version_string定义在文件cmd/version.c中,定义如下: - const char __weak version_string[] =U_BOOT_VERSION_STRING;
复制代码 U_BOOT_VERSION_STRING是个宏,定义在文件include/version.h,如下: - #define U_BOOT_VERSION_STRING U_BOOT_VERSION" (" U_BOOT_DATE " - " \
- U_BOOT_TIME" " U_BOOT_TZ ")" CONFIG_IDENT_STRING
复制代码 U_BOOT_VERSION 定义在文件include/generated/version_autogenerated.h中,文件version_autogenerated.h内如如下: 示例代码32.2.9.4version_autogenerated.h文件代码 - #definePLAIN_VERSION "2016.03"
- #defineU_BOOT_VERSION "U-Boot " PLAIN_VERSION
- #defineCC_VERSION_STRING "arm-linux-gnueabihf-gcc(Linaro GCC 4.9-2017.01) 4.9.4"
- #defineLD_VERSION_STRING "GNU ld(Linaro_Binutils-2017.01) 2.24.0.20141017 Linaro 2014_11-3-git"
复制代码 可以看出,U_BOOT_VERSION为“U-boot 2016.03”, U_BOOT_DATE、U_BOOT_TIME和U_BOOT_TZ这定义在文件include/generated/timestamp_autogenerated.h中,如下所示: 示例代码32.2.9.5timestamp_autogenerated.h文件代码 - #define U_BOOT_DATE "Apr 25 2019"
- #define U_BOOT_TIME "21:10:53"
- #define U_BOOT_TZ "+0800"
- #define U_BOOT_DMI_DATE "04/25/2019"
复制代码宏CONFIG_IDENT_STRING为空,所以U_BOOT_VERSION_STRING为“U-Boot 2016.03 (Apr 25 2019 - 21:10:53 +0800)”,进入uboot命令模式,输入命令“version”查看版本号,如图32.2.9.1所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image074.jpg 图32.2.9.1 版本查询 图32.2.9.1中的第一行就是uboot版本号,和我们分析的一致。 接着回到示例代码32.2.9.2中,第60行,cli_init函数,跟命令初始化有关,初始化hushshell相关的变量。 第62行,run_preboot_environment_command函数,获取环境变量perboot的内容,perboot是一些预启动命令,一般不使用这个环境变量。 第68行,bootdelay_process函数,此函数会读取环境变量bootdelay和bootcmd的内容,然后将bootdelay的值赋值给全局变量stored_bootdelay,返回值为环境变量bootcmd的值。 第69行,如果定义了CONFIG_OF_CONTROL的话函数cli_process_fdt就会实现,如果没有定义CONFIG_OF_CONTROL的话函数cli_process_fdt直接返回一个false。在本uboot中没有定义CONFIG_OF_CONTROL,因此cli_process_fdt函数返回值为false。 第72行,autoboot_command函数,此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?此函数定义在文件common/autoboot.c中,内容如下: 示例代码32.2.9.5 auboboot.c文件代码段 - void autoboot_command(constchar*s)
- {
- debug("### main_loop:bootcmd="%s"\n", s ? s :"<UNDEFINED>");
- if(stored_bootdelay !=-1&& s &&!abortboot(stored_bootdelay)){
- #if defined(CONFIG_AUTOBOOT_KEYED)&&!defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
- int prev = disable_ctrlc(1);/* disable Control C checking */
- #endif
- run_command_list(s,-1,0);
- #if defined(CONFIG_AUTOBOOT_KEYED)&&!defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
- disable_ctrlc(prev);/* restore ControlC checking */
- #endif
- }
- #ifdefCONFIG_MENUKEY
- if(menukey == CONFIG_MENUKEY){
- s = getenv("menucmd");
- if(s)
- run_command_list(s,-1,0);
- }
- #endif /* CONFIG_MENUKEY */
- }
复制代码 可以看出,autoboot_command函数里面有很多条件编译,条件编译一多就不利于我们阅读程序(所以正点原子的例程基本是不用条件编译的,就是为了方便大家阅读源码)!宏CONFIG_AUTOBOOT_KEYED、CONFIG_AUTOBOOT_KEYED_CTRLC和CONFIG_MENUKEY这三个宏在I.MX6ULL里面没有定义,所以讲示例代码32.2.9.5进行精简,得到如下代码: 示例代码32.2.9.6autoboot_command函数精简版本 - void autoboot_command(constchar*s)
- {
- if(stored_bootdelay !=-1&& s &&!abortboot(stored_bootdelay)){
- run_command_list(s,-1,0);
- }
- }
复制代码 当一下三条全部成立的话,就会执行函数run_command_list。 ①、stored_bootdelay不等于-1。 ②、s不为空。 ③、函数abortboot返回值为0。 stored_bootdelay等于环境变量bootdelay的值;s是环境变量bootcmd的值,一般不为空,因此前两个成立,就剩下了函数abortboot的返回值,abortboot函数也定义在文件common/autoboot.c中,内容如下: 示例代码32.2.9.7 abortboot函数 - staticint abortboot(int bootdelay)
- {
- #ifdefCONFIG_AUTOBOOT_KEYED
- return abortboot_keyed(bootdelay);
- #else
- return abortboot_normal(bootdelay);
- #endif
- }
复制代码 因为宏CONFIG_AUTOBOOT_KEYE未定义,因此执行函数abortboot_normal,好吧,绕来绕去的!接着来看函数abortboot_normal,此函数也定义在文件common/autoboot.c中,内容如下: 示例代码32.2.9.8abortboot_normal函数 - staticint abortboot_normal(int bootdelay)
- {
- int abort =0;
- unsignedlong ts;
- #ifdefCONFIG_MENUPROMPT
- printf(CONFIG_MENUPROMPT);
- #else
- if(bootdelay >=0)
- printf("Hit any key to stop autoboot: %2d ", bootdelay);
- #endif
- #if definedCONFIG_ZERO_BOOTDELAY_CHECK
- /*
- * Check if key already pressed
- * Don't check if bootdelay < 0
- */
- if(bootdelay >=0){
- if(tstc()){/* we got a keypress */
- (void) getc();/* consume input */
- puts("\b\b\b 0");
- abort =1;/* don't auto boot */
- }
- }
- #endif
- while((bootdelay >0)&&(!abort)){
- --bootdelay;
- /* delay 1000 ms */
- ts = get_timer(0);
- do{
- if(tstc()){/* we got a keypress */
- abort =1;/* don't auto boot */
- bootdelay =0;/* no moredelay */
- # ifdefCONFIG_MENUKEY
- menukey = getc();
- # else
- (void) getc();/* consume input */
- # endif
- break;
- }
- udelay(10000);
- }while(!abort && get_timer(ts)<1000);
- printf("\b\b\b%2d ", bootdelay);
- }
- putc('\n');
- #ifdefCONFIG_SILENT_CONSOLE
- if(abort)
- gd->flags &=~GD_FLG_SILENT;
- #endif
- return abort;
- }
复制代码 函数abortboot_normal同样很多条件编译,删除掉条件编译相关代码后abortboot_normal函数内容如下: 示例代码32.2.9.9abortboot_normal函数精简 - staticint abortboot_normal(int bootdelay)
- {
- int abort =0;
- unsignedlong ts;
- if(bootdelay >=0)
- printf("Hit any key to stop autoboot: %2d ", bootdelay);
- while((bootdelay >0)&&(!abort)){
- --bootdelay;
- /* delay 1000 ms */
- ts = get_timer(0);
- do{
- if(tstc()){/* we got a keypress */
- abort =1;/* don't autoboot */
- bootdelay =0;/* no more delay */
- (void) getc();/* consume input */
- break;
- }
- udelay(10000);
- }while(!abort && get_timer(ts)<1000);
- printf("\b\b\b%2d ", bootdelay);
- }
- putc('\n');
- return abort;
- }
复制代码 第3行的变量abort是函数abortboot_normal的返回值,默认值为0。 第7行通过串口输出“Hit any key to stop autoboot”字样,如图32.2.9.2所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image076.jpg 图32.2.9.2 倒计时 第9~21行就是倒计时的具体实现。 第14行判断键盘是否有按下,也就是是否打断了倒计时,如果键盘按下的话就执行相应的分支。比如设置abort为1,设置bootdelay为0等,最后跳出倒计时循环。 第26行,返回abort的值,如果倒计时自然结束,没有被打断abort就为0,否则的话abort的值就为1。 回到示例代码32.2.9.6的autoboot_command函数中,如果倒计时自然结束那么就执行函数run_command_list,此函数会执行参数s指定的一系列命令,也就是环境变量bootcmd的命令,bootcmd里面保存着默认的启动命令,因此linux内核启动!这个就是uboot中倒计时结束以后自动启动linux内核的原理。如果倒计时结束之前按下了键盘上的按键,那么run_command_list函数就不会执行,相当于autoboot_command是个空函数。 回到“遥远”的示例代码32.2.9.2中的main_loop函数中,如果倒计时结束之前按下按键,那么就会执行第74行的cli_loop函数,这个就是命令处理函数,负责接收好处理输入的命令。 32.2.10 cli_loop函数详解 cli_loop函数是uboot的命令行处理函数,我们在uboot中输入各种命令,进行各种操作就是有cli_loop来处理的,此函数定义在文件common/cli.c中,函数内容如下: 示例代码32.2.10.1 cli.c文件代码段 - void cli_loop(void)
- {
- #ifdefCONFIG_SYS_HUSH_PARSER
- parse_file_outer();
- /* This point is neverreached */
- for(;;);
- #else
- cli_simple_loop();
- #endif /*CONFIG_SYS_HUSH_PARSER*/
- }
复制代码 在文件include/configs/mx6_common.h中有定义宏CONFIG_SYS_HUSH_PARSER,而正点原子的I.MX6ULL开发板配置头文件mx6ullevk.h里面会引用mx_common.h这个头文件,因此宏CONFIG_SYS_HUSH_PARSER有定义。 第205行调用函数parse_file_outer。 第207行是个死循环,永远不会执行到这里。 函数parse_file_outer定义在文件common/cli_hush.c中,去掉条件编译内容以后的函数内容如下: 示例代码32.2.10.2parse_file_outer函数精简 - int parse_file_outer(void)
- {
- int rcode;
- struct in_str input;
- setup_file_in_str(&input);
- rcode = parse_stream_outer(&input,FLAG_PARSE_SEMICOLON);
- return rcode;
- }
复制代码 第3行调用函数setup_file_in_str初始化变量input的成员变量。 第4行调用函数parse_stream_outer,这个函数就是hushshell的命令解释器,负责接收命令行输入,然后解析并执行相应的命令,函数parse_stream_outer定义在文件common/cli_hush.c中,精简版的函数内容如下: 示例代码32.2.10.3 parse_stream_outer函数精简 - staticint parse_stream_outer(struct in_str *inp,int flag)
- {
- struct p_context ctx;
- o_string temp=NULL_O_STRING;
- int rcode;
- int code =1;
- do{
- ......
- rcode = parse_stream(&temp,&ctx, inp,
- flag &FLAG_CONT_ON_NEWLINE ?-1:'\n');
- ......
- if(rcode !=1&& ctx.old_flag ==0){
- ......
- run_list(ctx.list_head);
- ......
- }else{
- ......
- }
- b_free(&temp);
- /* loop on syntaxerrors, return on EOF */
- }while(rcode !=-1&&!(flag & FLAG_EXIT_FROM_LOOP)&&
- (inp->peek != static_peek || b_peek(inp)));
- return0;
- }
复制代码 第7~21行中的do-while循环就是处理输入命令的。 第9行调用函数parse_stream进行命令解析。 第14行调用调用run_list函数来执行解析出来的命令。 函数run_list会经过一系列的函数调用,最终通过调用cmd_process函数来处理命令,过程如下: 示例代码32.2.10.4 run_list执行流程 - staticint run_list(struct pipe *pi)
- {
- int rcode=0;
- rcode = run_list_real(pi);
- ......
- return rcode;
- }
- staticint run_list_real(struct pipe *pi)
- {
- char*save_name =NULL;
- ......
- int if_code=0, next_if_code=0;
- ......
- rcode = run_pipe_real(pi);
- ......
- return rcode;
- }
- staticint run_pipe_real(struct pipe *pi)
- {
- int i;
- int nextin;
- int flag = do_repeat ? CMD_FLAG_REPEAT :0;
- struct child_prog *child;
- char*p;
- ......
- if(pi->num_progs ==1) child =&(pi->progs[0]);
- ......
- return rcode;
- }elseif(pi->num_progs ==1&& pi->progs[0].argv !=NULL){
- ......
- /* Process thecommand */
- return cmd_process(flag, child->argc, child->argv,
- &flag_repeat,NULL);
- }
- return-1;
- }
复制代码 第5行,run_list调用run_list_real函数。 第16行,run_list_real函数调用run_pipe_real函数。 第36行,run_pipe_real函数调用cmd_process函数。 最终通过函数cmd_process来处理命令,接下来就是分析cmd_process函数。 32.2.11cmd_process函数详解在学习cmd_process之前先看一下uboot中命令是如何定义的。uboot使用宏U_BOOT_CMD来定义命令,宏U_BOOT_CMD定义在文件include/command.h中,定义如下: 示例代码32.2.11.1 U_BOOT_CMD宏定义 - #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage,_help) \
- U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
复制代码 可以看出U_BOOT_CMD是U_BOOT_CMD_COMPLETE的特例,将U_BOOT_CMD_COMPLETE的最后一个参数设置成NULL就是U_BOOT_CMD。宏U_BOOT_CMD_COMPLETE如下: 示例代码32.2.11.2U_BOOT_CMD_COMPLETE宏定义 - #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd,_usage, _help, _comp) \
- ll_entry_declare(cmd_tbl_t, _name, cmd) = \
- U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
- _usage, _help, _comp);
复制代码 宏U_BOOT_CMD_COMPLETE又用到了ll_entry_declare和U_BOOT_CMD_MKENT_COMPLETE。ll_entry_declar定义在文件include/linker_lists.h中,定义如下: 示例代码32.2.11.3ll_entry_declare宏定义 - #define ll_entry_declare(_type, _name, _list) \
- _type_u_boot_list_2_##_list##_2_##_name __aligned(4) \
- __attribute__((unused, \
- section(".u_boot_list_2_"#_list"_2_"#_name)))
复制代码 _type为cmd_tbl_t,因此ll_entry_declare就是定义了一个cmd_tbl_t变量,这里用到了C语言中的“##”连接符符。其中的“##_list”表示用_list的值来替换,“##_name”就是用_name的值来替换。 宏U_BOOT_CMD_MKENT_COMPLETE定义在文件include/command.h中,内容如下: 示例代码32.2.11.4U_BOOT_CMD_MKENT_COMPLETE宏定义 - #define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep,_cmd, \
- _usage, _help, _comp) \
- { #_name,_maxargs, _rep, _cmd, _usage, \
- _CMD_HELP(_help) _CMD_COMPLETE(_comp) }
复制代码 上述代码中的“#”表示将_name传递过来的值字符串化,U_BOOT_CMD_MKENT_COMPLETE又用到了宏_CMD_HELP和_CMD_COMPLETE,这两个宏的定义如下: 示例代码32.2.11.5 _CMD_HELP和_CMD_COMPLETE宏定义 - 1 #ifdef CONFIG_AUTO_COMPLETE
- 2 # define _CMD_COMPLETE(x) x,
- 3 #else
- 4 # define _CMD_COMPLETE(x)
- 5 #endif
- 6 #ifdef CONFIG_SYS_LONGHELP
- 7 # define _CMD_HELP(x) x,
- 8 #else
- 9 # define _CMD_HELP(x)
- 10 #endif
复制代码 可以看出,如果定义了宏CONFIG_AUTO_COMPLETE和CONFIG_SYS_LONGHELP的话,_CMD_COMPLETE和_CMD_HELP就是取自身的值,然后在加上一个‘,’。CONFIG_AUTO_COMPLETE和CONFIG_SYS_LONGHELP这两个宏有定义在文件mx6_common.h中。 U_BOOT_CMD宏的流程我们已经清楚了(一个U_BOOT_CMD宏就如此的绕来绕去的!),我们就以一个具体的命令为例,来看一下U_BOOT_CMD经过展开以后究竟是个什么模样的。以命令dhcp为例,dhcp命令定义如下: 示例代码32.2.11.6 dhcp命令宏定义 - U_BOOT_CMD(
- dhcp,3,1, do_dhcp,
- "boot image via network using DHCP/TFTPprotocol",
- "[loadAddress] [[hostIPaddr:]bootfilename]"
- );
复制代码 将其展开,结果如下: 示例代码32.2.11.7 dhcp命令展开 - U_BOOT_CMD(
- dhcp,3,1, do_dhcp,
- "boot image via network using DHCP/TFTPprotocol",
- "[loadAddress] [[hostIPaddr:]bootfilename]"
- );
- 1、将U_BOOT_CMD展开后为:
- U_BOOT_CMD_COMPLETE(dhcp,3,1, do_dhcp,
- "boot image via network using DHCP/TFTPprotocol",
- "[loadAddress] [[hostIPaddr:]bootfilename]",
- NULL)
- 2、将U_BOOT_CMD_COMPLETE展开后为:
- ll_entry_declare(cmd_tbl_t, dhcp, cmd)= \
- U_BOOT_CMD_MKENT_COMPLETE(dhcp,3,1, do_dhcp, \
- "boot image via network using DHCP/TFTPprotocol", \
- "[loadAddress] [[hostIPaddr:]bootfilename]", \
- NULL);
- 3、将ll_entry_declare和U_BOOT_CMD_MKENT_COMPLETE展开后为:
- cmd_tbl_t _u_boot_list_2_cmd_2_dhcp __aligned(4) \
- __attribute__((unused,section(.u_boot_list_2_cmd_2_dhcp))) \
- {"dhcp",3,1, do_dhcp, \
- "boot image via network using DHCP/TFTPprotocol", \
- "[loadAddress] [[hostIPaddr:]bootfilename]",\
- NULL}
复制代码 从示例代码32.2.11.7可以看出,dhcp命令最终展开结果为: 示例代码32.2.11.8 dhcp命令最终结果 - cmd_tbl_t_u_boot_list_2_cmd_2_dhcp __aligned(4) \
- __attribute__((unused,section(.u_boot_list_2_cmd_2_dhcp))) \
- {"dhcp",3,1, do_dhcp, \
- "boot image vianetwork using DHCP/TFTP protocol", \
- "[loadAddress][[hostIPaddr:]bootfilename]",\
- NULL}
复制代码 第1行定义了一个cmd_tbl_t类型的变量,变量名为_u_boot_list_2_cmd_2_dhcp,此变量4字节对齐。 第2行,使用__attribute__关键字设置变量_u_boot_list_2_cmd_2_dhcp存储在.u_boot_list_2_cmd_2_dhcp段中。u-boot.lds链接脚本中有一个名为“.u_boot_list”的段,所有.u_boot_list开头的段都存放到.u_boot.list中,如图32.2.11.1所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image078.jpg 图32.2.11.1 u-boot.lds中的.u_boot_list段 因此,第2行就是设置变量_u_boot_list_2_cmd_2_dhcp的存储位置。 第3~6行,cmd_tbl_t是个结构体,因此第3-6行是初始化cmd_tbl_t这个结构体的各个成员变量。cmd_tbl_t结构体定义在文件include/command.h中,内容如下: 示例代码32.2.11.9 cmd_tbl_t结构体 - struct cmd_tbl_s {
- char *name; /* CommandName */
- int maxargs; /* maximumnumber of arguments */
- int repeatable; /*autorepeat allowed? */
- /* Implementationfunction */
- int(*cmd)(struct cmd_tbl_s *,int,int,char*const[]);
- char*usage; /* Usagemessage (short) */
- #ifdef CONFIG_SYS_LONGHELP
- char*help; /* Help message (long) */
- #endif
- #ifdefCONFIG_AUTO_COMPLETE
- /* do auto completion on the arguments */
- int(*complete)(int argc,char*const argv[],char last_char,int maxv,char*cmdv[]);
- #endif
- };
- typedefstruct cmd_tbl_scmd_tbl_t;
复制代码 结合实例代码32.2.11.8,可以得出变量_u_boot_list_2_cmd_2_dhcp的各个成员的值如下所示: - _u_boot_list_2_cmd_2_dhcp.name = "dhcp"
- _u_boot_list_2_cmd_2_dhcp.maxargs = 3
- _u_boot_list_2_cmd_2_dhcp.repeatable = 1
- _u_boot_list_2_cmd_2_dhcp.cmd = do_dhcp
- _u_boot_list_2_cmd_2_dhcp.usage = "boot imagevia network using DHCP/TFTP protocol"
- _u_boot_list_2_cmd_2_dhcp.help = "[loadAddress][[hostIPaddr:]bootfilename]"
- _u_boot_list_2_cmd_2_dhcp.complete = NULL
复制代码 当我们在uboot的命令行中输入“dhcp”这个命令的时候,最终执行的是do_dhcp这个函数。总结一下,uboot中使用U_BOOT_CMD来定义一个命令,最终的目的就是为了定义一个cmd_tbl_t类型的变量,并初始化这个变量的各个成员。uboot中的每个命令都存储在.u_boot_list段中,每个命令都有一个名为do_xxx(xxx为具体的命令名)的函数,这个do_xxx函数就是具体的命令处理函数。 了解了uboot中命令的组成以后,再来看一下cmd_process函数的处理过程,cmd_process函数定义在文件common/command.c中,函数内容如下: 示例代码32.2.11.10 command.c文件代码段 - enum command_ret_t cmd_process(int flag,int argc,
- char*const argv[],int*repeatable, ulong *ticks)
- {
- enum command_ret_t rc = CMD_RET_SUCCESS;
- cmd_tbl_t *cmdtp;
- /* Look up commandin command table */
- cmdtp = find_cmd(argv[0]);
- if(cmdtp ==NULL){
- printf("Unknown command '%s' - try'help'\n", argv[0]);
- return1;
- }
- /* found - checkmax args */
- if(argc > cmdtp->maxargs)
- rc = CMD_RET_USAGE;
- #if defined(CONFIG_CMD_BOOTD)
- /* avoid"bootd" recursion */
- elseif(cmdtp->cmd == do_bootd){
- if(flag & CMD_FLAG_BOOTD){
- puts("'bootd' recursion detected\n");
- rc = CMD_RET_FAILURE;
- }else{
- flag |= CMD_FLAG_BOOTD;
- }
- }
- #endif
- /* If OK so far,then do the command */
- if(!rc){
- if(ticks)
- *ticks = get_timer(0);
- rc = cmd_call(cmdtp, flag, argc, argv);
- if(ticks)
- *ticks = get_timer(*ticks);
- *repeatable &= cmdtp->repeatable;
- }
- if(rc == CMD_RET_USAGE)
- rc = cmd_usage(cmdtp);
- return rc;
- }
复制代码 第507行,调用函数find_cmd在命令表中找到指定的命令,find_cmd函数内容如下: 示例代码32.2.11.10 command.c文件代码段 - cmd_tbl_t *find_cmd(constchar*cmd)
- {
- cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
- constint len = ll_entry_count(cmd_tbl_t, cmd);
- return find_cmd_tbl(cmd, start, len);
- }
复制代码 参数cmd就是所查找的命令名字,uboot中的命令表其实就是cmd_tbl_t结构体数组,通过函数ll_entry_start得到数组的第一个元素,也就是命令表起始地址。通过函数ll_entry_count得到数组长度,也就是命令表的长度。最终通过函数find_cmd_tbl在命令表中找到所需的命令,每个命令都有一个name成员,所以将参数cmd与命令表中每个成员的name字段都对比一下,如果相等的话就说明找到了这个命令,找到以后就返回这个命令。 回到示例代码32.2.11.10的cmd_process函数中,找到命令以后肯定就要执行这个命令了,第533行调用函数cmd_call来执行具体的命令,cmd_call函数内容如下: 示例代码32.2.11.11 command.c文件代码段 - staticint cmd_call(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[])
- {
- int result;
- result =(cmdtp->cmd)(cmdtp, flag, argc, argv);
- if(result)
- debug("Command failed, result=%d\n", result);
- return result;
- }
复制代码在前面的分析中我们知道,cmd_tbl_t的cmd成员就是具体的命令处理函数,所以第494行调用cmdtp的cmd成员来处理具体的命令,返回值为命令的执行结果。 cmd_process中会检测cmd_tbl的返回值,如果返回值为CMD_RET_USAGE的话就会调用cmd_usage函数输出命令的用法,其实就是输出cmd_tbl_t的usage成员变量。 32.3 bootz启动Linux内核过程32.3.1 images全局变量 不管是bootz还是bootm命令,在启动Linux内核的时候都会用到一个重要的全局变量:images,images在文件cmd/bootm.c中有如下定义: 示例代码32.3.1.1 images全局变量 - bootm_headers_timages;/* pointersto os/initrd/fdt images */
复制代码 images是bootm_headers_t类型的全局变量,bootm_headers_t是个boot头结构体,在文件include/image.h中的定义如下(删除了一些条件编译代码): 示例代码32.3.1.2 bootm_headers_t结构体 - typedefstruct bootm_headers {
- /*
- * Legacy os image header, if it is a multicomponent image
- * then boot_get_ramdisk() and get_fdt()will attempt to get
- * data from second and third componentaccordingly.
- */
- image_header_t *legacy_hdr_os; /* image header pointer */
- image_header_t legacy_hdr_os_copy;/* header copy */
- ulong legacy_hdr_valid;
- ...
- #ifndef USE_HOSTCC
- image_info_t os; /* OS镜像信息 */
- ulong ep; /* OS入口点 */
- ulong rd_start, rd_end; /* ramdisk开始和结束位置 */
- char*ft_addr; /* 设备树地址 */
- ulong ft_len; /* 设备树长度 */
- ulong initrd_start; /* initrd开始位置 */
- ulong initrd_end; /* initrd结束位置 */
- ulong cmdline_start; /* cmdline开始位置 */
- ulong cmdline_end; /* cmdline结束位置 */
- bd_t *kbd;
- #endif
- int verify;/*getenv("verify")[0] != 'n' */
- #defineBOOTM_STATE_START (0x00000001)
- #defineBOOTM_STATE_FINDOS (0x00000002)
- #defineBOOTM_STATE_FINDOTHER (0x00000004)
- #defineBOOTM_STATE_LOADOS (0x00000008)
- #defineBOOTM_STATE_RAMDISK (0x00000010)
- #defineBOOTM_STATE_FDT (0x00000020)
- #defineBOOTM_STATE_OS_CMDLINE (0x00000040)
- #defineBOOTM_STATE_OS_BD_T (0x00000080)
- #defineBOOTM_STATE_OS_PREP (0x00000100)
- #defineBOOTM_STATE_OS_FAKE_GO (0x00000200)/*'Almost' run theOS*/
- #defineBOOTM_STATE_OS_GO (0x00000400)
- int state;
- #ifdef CONFIG_LMB
- struct lmb lmb;/* 内存管理相关,不深入研究 */
- #endif
- } bootm_headers_t;
复制代码 第335行的os成员变量是image_info_t类型的,为系统镜像信息。 第352~362行这11个宏定义表示BOOT的不同阶段。 接下来看一下结构体image_info_t,也就是系统镜像信息结构体,此结构体在文件include/image.h中的定义如下: 示例代码32.3.1.3 image_info_t结构体 - typedefstruct image_info {
- ulong start, end; /* blob开始和结束位置*/
- ulong image_start, image_len; /* 镜像起始地址(包括blob)和长度 */
- ulong load; /* 系统镜像加载地址*/
- uint8_t comp, type, os; /* 镜像压缩、类型,OS类型 */
- uint8_t arch; /* CPU架构 */
- } image_info_t;
复制代码 全局变量images会在bootz命令的执行中频繁使用到,相当于Linux内核启动的“灵魂”。 32.3.2 do_bootz函数 bootz命令的执行函数为do_bootz,在文件cmd/bootm.c中有如下定义: 示例代码32.3.2.1 do_bootz函数 - int do_bootz(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[])
- {
- int ret;
- /* Consume 'bootz'*/
- argc--; argv++;
- if(bootz_start(cmdtp, flag, argc, argv,&images))
- return1;
- /*
- * We are doing the BOOTM_STATE_LOADOSstate ourselves, so must
- * disable interrupts ourselves
- */
- bootm_disable_interrupts();
- images.os.os = IH_OS_LINUX;
- ret = do_bootm_states(cmdtp, flag, argc, argv,
- BOOTM_STATE_OS_PREP |BOOTM_STATE_OS_FAKE_GO |
- BOOTM_STATE_OS_GO,
- &images,1);
- return ret;
- }
复制代码 第629行,调用bootz_start函数,bootz_start函数执行过程参考32.3.3小节。 第636行,调用函数bootm_disable_interrupts关闭中断。 第638行,设置images.os.os为IH_OS_LINUX,也就是设置系统镜像为Linux,表示我们要启动的是Linux系统!后面会用到images.os.os来挑选具体的启动函数。 第639行,调用函数do_bootm_states来执行不同的BOOT阶段,这里要执行的BOOT阶段有:BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO和BOOTM_STATE_OS_GO。 32.3.3 bootz_start函数 bootz_srart函数也定义在文件cmd/bootm.c中,函数内容如下: 示例代码32.3.3.1 bootz_start函数 - staticint bootz_start(cmd_tbl_t *cmdtp,int flag,int argc,
- char*const argv[], bootm_headers_t *images)
- {
- int ret;
- ulong zi_start, zi_end;
- ret = do_bootm_states(cmdtp, flag, argc, argv,
- BOOTM_STATE_START, images,1);
- /* Setup Linuxkernel zImage entry point */
- if(!argc){
- images->ep = load_addr;
- debug("* kernel: default image load address = 0x%08lx\n",
- load_addr);
- }else{
- images->ep = simple_strtoul(argv[0],NULL,16);
- debug("* kernel: cmdline image address = 0x%08lx\n",
- images->ep);
- }
- ret = bootz_setup(images->ep,&zi_start,&zi_end);
- if(ret !=0)
- return1;
- lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
- /*
- * Handle the BOOTM_STATE_FINDOTHER stateourselves as we do not
- * have a header that provide thisinformaiton.
- */
- if(bootm_find_images(flag, argc, argv))
- return1;
- ......
- return0;
- }
复制代码 第584行,调用函数do_bootm_states,执行BOOTM_STATE_START阶段。 第593行,设置images的ep成员变量,也就是系统镜像的入口点,使用bootz命令启动系统的时候就会设置系统在DRAM中的存储位置,这个存储位置就是系统镜像的入口点,因此images->ep=0X80800000。 第598行,调用bootz_setup函数,此函数会判断当前的系统镜像文件是否为Linux的镜像文件,并且会打印出镜像相关信息,bootz_setup函数稍后会讲解。 第608行,调用函数bootm_find_images查找ramdisk和设备树(dtb)文件,但是我们没有用到ramdisk,因此此函数在这里仅仅用于查找设备树(dtb)文件,此函数稍后也会讲解。 先来看一下bootz_setup函数,此函数定义在文件arch/arm/lib/bootm.c中,函数内容如下: 示例代码32.3.3.2 bootz_setup函数 - #defineLINUX_ARM_ZIMAGE_MAGIC 0x016f2818
- int bootz_setup(ulong image, ulong *start, ulong *end)
- {
- struct zimage_header *zi;
- zi =(struct zimage_header *)map_sysmem(image,0);
- if(zi->zi_magic !=LINUX_ARM_ZIMAGE_MAGIC){
- puts("Bad Linux ARM zImage magic!\n");
- return1;
- }
- *start = zi->zi_start;
- *end = zi->zi_end;
- printf("Kernel image @ %#08lx [ %#08lx - %#08lx]\n", image,
- *start,*end);
- return0;
- }
复制代码 第370行,宏LINUX_ARM_ZIMAGE_MAGIC就是ARM Linux系统魔术数。 第376行,从传递进来的参数image(也就是系统镜像首地址)中获取zimage头。zImage头结构体为zimage_header。 第377~380行,判断image是否为ARM的Linux系统镜像,如果不是的话就直接返回,并且打印出“Bad Linux ARM zImage magic!”,比如我们输入一个错误的启动命令: - bootz 80000000 – 900000000
复制代码 因为我们并没有在0X80000000处存放Linux镜像文件(zImage),因此上面的命令肯定会执行出错的,结果如图32.3.3.1所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image080.jpg 图32.3.3.1 启动出错 第382、383行初始化函数bootz_setup的参数start和end。 第385行,打印启动信息,如果Linux系统镜像正常的话就会输出图32.3.3.2所示的信息: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image082.jpg 图32.3.3.3 Linux镜像信息 接下来看一下函数bootm_find_images,此函数定义在文件common/bootm.c中,函数内容如下: 示例代码32.3.3.3bootm_find_images函数 - int bootm_find_images(int flag,int argc,char*const argv[])
- {
- int ret;
- /* find ramdisk */
- ret = boot_get_ramdisk(argc, argv,&images, IH_INITRD_ARCH,
- &images.rd_start,&images.rd_end);
- if(ret){
- puts("Ramdisk image is corrupt orinvalid\n");
- return1;
- }
- #if defined(CONFIG_OF_LIBFDT)
- /* find flatteneddevice tree */
- ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT,&images,
- &images.ft_addr,&images.ft_len);
- if(ret){
- puts("Could not find a valid devicetree\n");
- return1;
- }
- set_working_fdt_addr((ulong)images.ft_addr);
- #endif
- ...
- return0;
- }
复制代码 第230~235行是跟查找ramdisk,但是我们没有用到ramdisk,因此这部分代码不用管。 第237~244行是查找设备树(dtb)文件,找到以后就将设备树的起始地址和长度分别写到images的ft_addr和ft_len成员变量中。我们使用bootz启动Linux的时候已经指明了设备树在DRAM中的存储地址,因此images.ft_addr=0X83000000,长度根据具体的设备树文件而定,比如我现在使用的设备树文件长度为0X8C81,因此images.ft_len=0X8C81。 bootz_start函数就讲解到这里,bootz_start主要用于初始化images的相关成员变量。 32.3.4 do_bootm_states函数 do_bootz最后调用的就是函数do_bootm_states,而且在bootz_start中也调用了do_bootm_states函数,看来do_bootm_states函数还是个香饽饽。此函数定义在文件common/bootm.c中,函数代码如下: 示例代码32.3.4.1 do_bootm_states函数 - int do_bootm_states(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[],
- int states, bootm_headers_t *images,int boot_progress)
- {
- boot_os_fn *boot_fn;
- ulong iflag =0;
- int ret =0, need_boot_fn;
- images->state |= states;
- /*
- * Work through the states and see how farwe get. We stop on
- * any error.
- */
- if(states & BOOTM_STATE_START)
- ret = bootm_start(cmdtp, flag, argc, argv);
- if(!ret &&(states & BOOTM_STATE_FINDOS))
- ret = bootm_find_os(cmdtp, flag, argc, argv);
- if(!ret &&(states &BOOTM_STATE_FINDOTHER)){
- ret = bootm_find_other(cmdtp, flag, argc, argv);
- argc =0;/* consume the args */
- }
- /* Load the OS */
- if(!ret &&(states & BOOTM_STATE_LOADOS)){
- ulong load_end;
- iflag = bootm_disable_interrupts();
- ret = bootm_load_os(images,&load_end,0);
- if(ret ==0)
- lmb_reserve(&images->lmb, images->os.load,
- (load_end - images->os.load));
- elseif(ret && ret != BOOTM_ERR_OVERLAP)
- goto err;
- elseif(ret == BOOTM_ERR_OVERLAP)
- ret =0;
- #if defined(CONFIG_SILENT_CONSOLE)&&!defined(CONFIG_SILENT_U_BOOT_ONLY)
- if(images->os.os == IH_OS_LINUX)
- fixup_silent_linux();
- #endif
- }
- /* Relocate theramdisk */
- #ifdefCONFIG_SYS_BOOT_RAMDISK_HIGH
- if(!ret &&(states &BOOTM_STATE_RAMDISK)){
- ulong rd_len = images->rd_end - images->rd_start;
- ret = boot_ramdisk_high(&images->lmb, images->rd_start,
- rd_len,&images->initrd_start,&images->initrd_end);
- if(!ret){
- setenv_hex("initrd_start", images->initrd_start);
- setenv_hex("initrd_end", images->initrd_end);
- }
- }
- #endif
- #if defined(CONFIG_OF_LIBFDT)&& defined(CONFIG_LMB)
- if(!ret &&(states & BOOTM_STATE_FDT)){
- boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
- ret = boot_relocate_fdt(&images->lmb,&images->ft_addr,
- &images->ft_len);
- }
- #endif
- /* From now on, weneed the OS boot function */
- if(ret)
- return ret;
- boot_fn = bootm_os_get_boot_func(images->os.os);
- need_boot_fn = states &(BOOTM_STATE_OS_CMDLINE |
- BOOTM_STATE_OS_BD_T |BOOTM_STATE_OS_PREP |
- BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
- if(boot_fn ==NULL&&need_boot_fn){
- if(iflag)
- enable_interrupts();
- printf("ERROR: booting os '%s' (%d) is notsupported\n",
- genimg_get_os_name(images->os.os), images->os.os);
- bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
- return1;
- }
- /* Call variousother states that are not generally used */
- if(!ret &&(states &BOOTM_STATE_OS_CMDLINE))
- ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
- if(!ret &&(states &BOOTM_STATE_OS_BD_T))
- ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
- if(!ret &&(states &BOOTM_STATE_OS_PREP))
- ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
- #ifdefCONFIG_TRACE
- /* Pretend to runthe OS, then run a user command */
- if(!ret &&(states &BOOTM_STATE_OS_FAKE_GO)){
- char*cmd_list = getenv("fakegocmd");
- ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
- images, boot_fn);
- if(!ret && cmd_list)
- ret = run_command_list(cmd_list,-1, flag);
- }
- #endif
- /* Check for unsupportedsubcommand. */
- if(ret){
- puts("subcommand not supported\n");
- return ret;
- }
- /* Now run the OS!We hope this doesn't return */
- if(!ret &&(states & BOOTM_STATE_OS_GO))
- ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
- images, boot_fn);
- ...
- return ret;
- }
复制代码 函数do_bootm_states根据不同的BOOT状态执行不同的代码段,通过如下代码来判断BOOT状态: 在do_bootz函数中会用到BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO和BOOTM_STATE_OS_GO这三个BOOT状态,bootz_start函数中会用到BOOTM_STATE_START这个BOOT状态。为了精简代码,方便分析,因此我们将示例代码32.3.4.1中的函数do_bootm_states进行精简,只留下下面这4个BOOT状态对应的处理代码: - BOOTM_STATE_OS_PREP
- BOOTM_STATE_OS_FAKE_GO
- BOOTM_STATE_OS_GO
- BOOTM_STATE_START
复制代码 精简以后的do_bootm_states函数如下所示: 示例代码32.3.4.2 精简后的do_bootm_states函数 - int do_bootm_states(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[],
- int states, bootm_headers_t *images,int boot_progress)
- {
- boot_os_fn *boot_fn;
- ulong iflag =0;
- int ret =0, need_boot_fn;
- images->state |= states;
- /*
- * Work through the states and see how farwe get. We stop on
- * any error.
- */
- if(states & BOOTM_STATE_START)
- ret = bootm_start(cmdtp, flag, argc, argv);
- ...
- /* From now on, weneed the OS boot function */
- if(ret)
- return ret;
- boot_fn = bootm_os_get_boot_func(images->os.os);
- need_boot_fn = states &(BOOTM_STATE_OS_CMDLINE |
- BOOTM_STATE_OS_BD_T |BOOTM_STATE_OS_PREP |
- BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
- if(boot_fn ==NULL&&need_boot_fn){
- if(iflag)
- enable_interrupts();
- printf("ERROR: booting os '%s' (%d) is notsupported\n",
- genimg_get_os_name(images->os.os), images->os.os);
- bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
- return1;
- }
- ...
- if(!ret &&(states &BOOTM_STATE_OS_PREP))
- ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
- #ifdefCONFIG_TRACE
- /* Pretend to runthe OS, then run a user command */
- if(!ret &&(states &BOOTM_STATE_OS_FAKE_GO)){
- char*cmd_list = getenv("fakegocmd");
- ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
- images, boot_fn);
- if(!ret && cmd_list)
- ret = run_command_list(cmd_list,-1, flag);
- }
- #endif
- /* Check forunsupported subcommand. */
- if(ret){
- puts("subcommand not supported\n");
- return ret;
- }
- /* Now run the OS!We hope this doesn't return */
- if(!ret &&(states & BOOTM_STATE_OS_GO))
- ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
- images, boot_fn);
- ...
- return ret;
- }
复制代码 第604、605行,处理BOOTM_STATE_START阶段,bootz_start会执行这一段代码,这里调用函数bootm_start,此函数定义在文件common/bootm.c中,函数内容如下: 示例代码32.3.4.2 bootm_start函数 - staticint bootm_start(cmd_tbl_t *cmdtp,int flag,int argc,
- char*const argv[])
- {
- memset((void*)&images,0,sizeof(images));/* 清空images */
- images.verify = getenv_yesno("verify");/* 初始化verfify成员 */
- boot_start_lmb(&images);
- bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START,"bootm_start");
- images.state = BOOTM_STATE_START;/* 设置状态为BOOTM_STATE_START */
- return0;
- }
复制代码 接着回到示例代码32.3.4.2中,继续分析函数do_bootm_states。第658行非常重要!通过函数bootm_os_get_boot_func来查找系统启动函数,参数images->os.os就是系统类型,根据这个系统类型来选择对应的启动函数,在do_bootz中设置images.os.os= IH_OS_LINUX。函数返回值就是找到的系统启动函数,这里找到的Linux系统启动函数为do_bootm_linux,关于此函数查找系统启动函数的过程请参考32.3.5小节。因此boot_fn=do_bootm_linux,后面执行boot_fn函数的地方实际上是执行的do_bootm_linux函数。 第676行,处理BOOTM_STATE_OS_PREP状态,调用函数do_bootm_linux,do_bootm_linux也是调用boot_prep_linux来完成具体的处理过程。boot_prep_linux主要用于处理环境变量bootargs,bootargs保存着传递给Linuxkernel的参数。 第679~689行是处理BOOTM_STATE_OS_FAKE_GO状态的,但是要我们没用使能TRACE功能,因此宏CONFIG_TRACE也就没有定义,所以这段程序不会编译。 第699行,调用函数boot_selected_os启动Linux内核,此函数第4个参数为Linux系统镜像头,第5个参数就是Linux系统启动函数do_bootm_linux。boot_selected_os函数定义在文件common/bootm_os.c中,函数内容如下: 示例代码32.3.4.3boot_selected_os函数 - int boot_selected_os(int argc,char*const argv[],int state,
- bootm_headers_t *images, boot_os_fn *boot_fn)
- {
- arch_preboot_os();
- boot_fn(state, argc, argv, images);
- ...
- return BOOTM_ERR_RESET;
- }
复制代码 第480行调用boot_fn函数,也就是do_bootm_linux函数来启动Linux内核。 32.3.5 bootm_os_get_boot_func函数 do_bootm_states会调用bootm_os_get_boot_func来查找对应系统的启动函数,此函数定义在文件common/bootm_os.c中,函数内容如下: 示例代码32.3.5.1bootm_os_get_boot_func函数 - boot_os_fn *bootm_os_get_boot_func(int os)
- {
- #ifdefCONFIG_NEEDS_MANUAL_RELOC
- staticbool relocated;
- if(!relocated){
- int i;
- /* relocate bootfunction table */
- for(i =0; i < ARRAY_SIZE(boot_os); i++)
- if(boot_os[i]!=NULL)
- boot_os[i]+= gd->reloc_off;
- relocated = true;
- }
- #endif
- return boot_os[os];
- }
复制代码 第495~508行是条件编译,在本uboot中没有用到,因此这段代码无效,只有509行有效。在509行中boot_os是个数组,这个数组里面存放着不同的系统对应的启动函数。boot_os也定义在文件common/bootm_os.c中,如下所示: 示例代码32.3.5.2 boot_os数组 - static boot_os_fn *boot_os[]={
- [IH_OS_U_BOOT]= do_bootm_standalone,
- #ifdefCONFIG_BOOTM_LINUX
- [IH_OS_LINUX]= do_bootm_linux,
- #endif
- ...
- #ifdefCONFIG_BOOTM_OPENRTOS
- [IH_OS_OPENRTOS]= do_bootm_openrtos,
- #endif
- };
复制代码 第438行就是Linux系统对应的启动函数:do_bootm_linux。 32.3.6 do_bootm_linux函数 经过前面的分析,我们知道了do_bootm_linux就是最终启动Linux内核的函数,此函数定义在文件arch/arm/lib/bootm.c,函数内容如下: 示例代码32.3.6.1 do_bootm_linux函数 - <blockquote>int do_bootm_linux(int flag,int argc,char*const argv[],
复制代码 第351行,如果参数flag等于BOOTM_STATE_OS_GO或者BOOTM_STATE_OS_FAKE_GO的话就执行boot_jump_linux函数。boot_selected_os函数在调用do_bootm_linux的时候会将flag设置为BOOTM_STATE_OS_GO。 第352行,执行函数boot_jump_linux(又来了一个函数,绕啊绕啊!心累!),此函数定义在文件arch/arm/lib/bootm.c中,函数内容如下: 示例代码32.3.6.2 boot_jump_linux函数 - staticvoid boot_jump_linux(bootm_headers_t *images,int flag)
- {
- #ifdefCONFIG_ARM64
- ...
- #else
- unsignedlong machid = gd->bd->bi_arch_number;
- char*s;
- void(*kernel_entry)(int zero,int arch, uint params);
- unsignedlong r2;
- int fake =(flag & BOOTM_STATE_OS_FAKE_GO);
- kernel_entry =(void(*)(int,int, uint))images->ep;
- s = getenv("machid");
- if(s){
- if(strict_strtoul(s,16,&machid)<0){
- debug("strict_strtoul failed!\n");
- return;
- }
- printf("Using machid 0x%lx fromenvironment\n", machid);
- }
- debug("## Transferring control to Linux (ataddress %08lx)" \
- "...\n",(ulong) kernel_entry);
- bootstage_mark(BOOTSTAGE_ID_RUN_OS);
- announce_and_cleanup(fake);
- if(IMAGE_ENABLE_OF_LIBFDT&& images->ft_len)
- r2 =(unsignedlong)images->ft_addr;
- else
- r2 = gd->bd->bi_boot_params;
- ...
- kernel_entry(0, machid, r2);
- }
- #endif
- }
复制代码 第274~292行是64位ARM芯片对应的代码,Cortex-A7是32位芯片,因此用不到。 第293行,变量machid保存机器ID,如果不使用设备树的话这个机器ID会被传递给Linux内核,Linux内核会在自己的机器ID列表里面查找是否存在与uboot传递进来的machid匹配的项目,如果存在就说Linux内核支持这个机器,那么Linux就会启动!如果使用设备树的话这个machid就无效了,设备树存有一个“兼容性”这个属性,Linux内核会比较“兼容性”属性的值(字符串)来查看是否支持这个机器。 第295行,函数kernel_entry,看名字“内核_进入”,说明此函数是进入Linux内核的,也就是最终的大boos!!此函数有三个参数:zero,arch,params,第一个参数zero同样为0;第二个参数为机器ID;第三个参数ATAGS或者设备树(DTB)首地址,ATAGS是传统的方法,用于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)。 第299行,获取kernel_entry函数,函数kernel_entry并不是uboot定义的,而是Linux内核定义的,Linux内核镜像文件的第一行代码就是函数kernel_entry,而images->ep保存着Linux内核镜像的起始地址,而起始地址保存的不正是Linux内核第一行代码! 第313行,调用函数announce_and_cleanup来打印一些信息并做一些清理工作,此函数定义在文件arch/arm/lib/bootm.c中,函数内容如下: 示例代码32.3.6.3announce_and_cleanup函数 - staticvoid announce_and_cleanup(int fake)
- {
- printf("\nStarting kernel ...%s\n\n", fake ?
- "(fake run fortracing)":"");
- bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF,"start_kernel");
- ....
- cleanup_before_linux();
- }
复制代码 第74行,在启动Linux之前输出“Startingkernel ...”信息,如图32.3.6.1所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image084.gif 图32.3.6.1 系统启动提示信息 第87行调用cleanup_before_linux函数做一些清理工作。 继续回到示例代码32.3.6.2的函数boot_jump_linux,第315~318行是设置寄存器r2的值?为什么要设置r2的值呢?Linux内核一开始是汇编代码,因此函数kernel_entry就是个汇编函数。向汇编函数传递参数要使用r0、r1和r2(参数数量不超过3个的时候),所以r2寄存器就是函数kernel_entry的第三个参数。 第316行,如果使用设备树的话,r2应该是设备树的起始地址,而设备树地址保存在images的ftd_addr成员变量中。 第317行,如果不使用设备树的话,r2应该是uboot传递给Linux的参数起始地址,也就是环境变量bootargs的值, 第328行,调用kernel_entry函数进入Linux内核,此行将一去不复返,uboot的使命也就完成了,它可以安息了! 总结一下bootz命令的执行过程,如图32.3.6.2所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image086.gif 图32.3.6.2 bootz命令执行过程 到这里uboot的启动流程我们就讲解完成了,加上uboot顶层Makefile的分析,洋洋洒洒100多页,还是不少的!这也仅仅是uboot启动流程分析,当缕清了uboot的启动流程以后,后面移植uboot就会轻松很多。其实在工作中我们基本不需要这么详细的去了解uboot,半导体厂商提供给我们的uboot一般是可以直接用的,只要能跑起来,可以使用就可以了。但是作为学习,我们是必须去详细的了解一下uboot的启动流程,否则如果在工作中遇到问题我们连解决的方法都没有,都不知道该从哪里看起。但是呢,如果第一次就想弄懂uboot的整个启动流程还是有点困难的,所以如果没有看懂的话,不要紧!不要气馁,大多数人第一次看uboot启动流程基本都有各种各样的问题。 题外话: 相信大家看完本章以后基本都有一个感觉:长、复杂、绕!没错,当我第一次学习uboot的时候看到uboot启动流程的时候也是这个感觉,当时我也一脸懵逼,怎么这么复杂,这么长呢?尤其前面的汇编代码部分,还要涉及到ARM处理器架构的内容,当时也怀疑过自己是不是搞这一块的料。不过好在自己坚持下来了,uboot的启动流程我至少分析过7,8遍,各种版本的,零几年很古老的;12年、14年比较新的等等很多个版本的uboot。就I.MX6ULL使用的这个2016.03版本uboot我至少详细的分析了2遍,直至写完本章,大概花了1个月的时间。这期间查阅了各种资料,看了不知道多少篇博客,在这里感谢那些无私奉献的网友们。 相信很多朋友看完本章可能会想:我什么时候也能这么厉害,能够这么详细的分析uboot启动流程。甚至可能会有挫败感,还是那句话:不要气馁!千里之行始于足下,所有你羡慕的人都曾经痛苦过,挫败过。脚踏实地,一步一个脚印,一点一滴的积累,最终你也会成为你所羡慕的人。在嵌入式Linux这条道路上,有众多的学习者陪着你,大家相互搀扶,终能踏出一条康庄大道,祝所有的同学终有所获!
|