您当前的位置: 首页 >  嵌入式

正点原子

暂无认证

  • 1浏览

    0关注

    382博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【正点原子Linux连载】第三十二章 U-Boot启动流程详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

正点原子 发布时间:2021-08-27 12:07:58 ,浏览量:1

1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html 3)对正点原子Linux感兴趣的同学可以加群讨论:935446741 4)关注正点原子公众号,获取最新资料更新 在这里插入图片描述

第三十二章 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所示: 在这里插入图片描述

图32.1.1 链接脚本 只有编译u-boot以后才会在根目录下出现u-boot.lds文件! 打开u-boot.lds,内容如下: 示例代码32.1.1 u-boot.lds文件代码

1  OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
2  OUTPUT_ARCH(arm)
3  ENTRY(_start)
4  SECTIONS
5  {
6   . = 0x00000000;
7   . = ALIGN(4);
8   .text :
9   {
10   *(.__image_copy_start)
11   *(.vectors)
12   arch/arm/cpu/armv7/start.o (.text*)
13   *(.text*)
14  }
15  . = ALIGN(4);
16  .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
17  . = ALIGN(4);
18  .data : {
19   *(.data*)
20  }
21  . = ALIGN(4);
22  . = .;
23  . = ALIGN(4);
24  .u_boot_list : {
25   KEEP(*(SORT(.u_boot_list*)));
26  }
27  . = ALIGN(4);
28  .image_copy_end :
29  {
30   *(.__image_copy_end)
31  }
32  .rel_dyn_start :
33  {
34   *(.__rel_dyn_start)
35  }
36  .rel.dyn : {
37   *(.rel*)
38  }
39  .rel_dyn_end :
40  {
41   *(.__rel_dyn_end)
42  }
43  .end :
44  {
45   *(.__end)
46  }
47  _image_binary_end = .;
48  . = ALIGN(4096);
49  .mmutable : {
50   *(.mmutable)
51  }
52  .bss_start __rel_dyn_start (OVERLAY) : {
53   KEEP(*(.__bss_start));
54   __bss_base = .;
55  }
56  .bss __bss_base (OVERLAY) : {
57   *(.bss*)
58    . = ALIGN(4);
59    __bss_limit = .;
60  }
61  .bss_end __bss_limit (OVERLAY) : {
62   KEEP(*(.__bss_end));
63  }
64  .dynsym _image_binary_end : { *(.dynsym) }
65  .dynbss : { *(.dynbss) }
66  .dynstr : { *(.dynstr*) }
67  .dynamic : { *(.dynamic*) }
68  .plt : { *(.plt*) }
69  .interp : { *(.interp*) }
70  .gnu.hash : { *(.gnu.hash) }
71  .gnu : { *(.gnu*) }
72  .ARM.exidx : { *(.ARM.exidx*) }
73  .gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
74 }
第3行为代码当前入口点:_start, _start在文件arch/arm/lib/vectors.S中有定义,如图32.1.2所示:

在这里插入图片描述

图32.1.2 _start入口 从图32.1.2中的代码可以看出,_start后面就是中断向量表,从图中的“.section “.vectors”, "ax”可以得到,此代码存放在.vectors段里面。 使用如下命令在uboot中查找“__image_copy_start”: grep -nR “__image_copy_start” 搜索结果如图32.1.3所示: 在这里插入图片描述

图32.1.3 查找结果 打开u-boot.map,找到如图32.1.4所示位置: 在这里插入图片描述

图32.1.4 u-boot.map u-boot.map是uboot的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,从图32.1.4的932行可以看到__image_copy_start为0X87800000,而.text的起始地址也是0X87800000。 继续回到示例代码32.1.1中,第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所示: 变量 数值 描述 __image_copy_start 0x87800000 uboot拷贝的首地址 __image_copy_end 0x8785dd54 uboot拷贝的结束地址 __rel_dyn_start 0x8785dd54 .rel.dyn段起始地址 __rel_dyn_end 0x878668f4 .rel.dyn段结束地址 _image_binary_end 0x878668f4 镜像结束地址 __bss_start 0x8785dd54 .bss段起始地址 __bss_end 0x878a8e74 .bss段结束地址 表32.1.1 uboot相关变量表 表32.1.1中的“变量”值可以在u-boot.map文件中查找,表32.1.1中除了__image_copy_start以外,其他的变量值每次编译的时候可能会变化,如果修改了uboot代码、修改了uboot配置、选用不同的优化等级等等都会影响到这些值。所以,一切以实际值为准! 32.2 U-Boot启动流程详解 32.2.1 reset函数源码详解 从u-boot.lds中我们已经知道了入口点是arch/arm/lib/vectors.S文件中的_start,代码如下: 示例代码32.2.1.1 vectors.S代码段

38 /*
39  *************************************************************
40  *
41  * Exception vectors as described in ARM reference manuals
42  *
43  * Uses indirect branch to allow reaching handlers anywhere in 
44  * memory.
45  **************************************************************
46  */
47 
48 _start:
49 
50 #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
51  .word   CONFIG_SYS_DV_NOR_BOOT_CFG
52 #endif
53 
54  	b   reset
55  	ldr pc, _undefined_instruction
56  	ldr pc, _software_interrupt
57  	ldr pc, _prefetch_abort
58  	ldr pc, _data_abort
59 		ldr pc, _not_used
60  	ldr pc, _irq
61  	ldr pc, _fiq

第48行_start开始的是中断向量表,其中54~61行就是中断向量表,和我们裸机例程里面一样。54行跳转到reset函数里面,reset函数在arch/arm/cpu/armv7/start.S里面,代码如下: 示例代码32.2.1.2 start.S代码段

22 /*****************************************************************
23  *
24  * Startup Code (reset vector)
25  *
26  * Do important init only if we don't start from memory!
27  * Setup memory and board specific bits prior to relocation.
28  * Relocate armboot to ram. Setup stack.
29  *
30  *****************************************************************/
31 
32  .globl  reset
33  .globl  save_boot_params_ret
34 
35 reset:
36 		/* Allow the board to save important registers */
37  	b   save_boot_params
第35行就是reset函数。
第37行从reset函数跳转到了save_boot_params函数,而save_boot_params函数同样定义在start.S里面,定义如下:

示例代码32.2.1.3 start.S代码段

91  /******************************************************************
92   *
93   * void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)
94   *  __attribute__((weak));
95   *
96   * Stack pointer is not yet initialized at this moment
97   * Don't save anything to stack even if compiled with -O0
98   *
99   ******************************************************************/
100 ENTRY(save_boot_params)
101     b   save_boot_params_ret        @ back to my caller
save_boot_params函数也是只有一句跳转语句,跳转到save_boot_params_ret函数,save_boot_params_ret函数代码如下:

示例代码32.2.1.4 start.S代码段

38 save_boot_params_ret:
39  	/*
40   	* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 
41  	* mode, except if in HYP mode already
42   	*/
43  	mrs 	r0, cpsr
44  	and 	r1, r0,	#0x1f  	@ mask mode bits
45  	teq 	r1, 		#0x1a  	@ test for HYP mode
46 	 	bicne 	r0, r0, 	#0x1f  	@ clear all mode bits
47  	orrne 	r0, r0, 	#0x13  	@ set SVC mode
48  	orr 	r0, r0, 	#0xc0  	@ disable FIQ and IRQ
49  	msr 	cpsr,r0
	第43行,读取寄存器cpsr中的值,并保存到r0寄存器中。
	第44行,将寄存器r0中的值与0X1F进行与运算,结果保存到r1寄存器中,目的就是提取cpsr的bit0~bit4这5位,这5位为M4 M3 M2 M1 M0,M[4:0]这五位用来设置处理器的工作模式,如表32.2.1.1所示:

M[4:0] 模式 10000 User(usr) 10001 FIQ(fiq) 10010 IRQ(irq) 10011 Supervisor(svc) 10110 Monitor(mon) 10111 Abort(abt) 11010 Hyp(hyp) 11011 Undefined(und) 11111 System(sys) 表32.2.1.1 Cortex-A7工作模式 第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代码段

51 /*
52  * Setup vector:
53  * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
54  * Continue to use ROM code vector only in OMAP4 spl)
55  */
56 #if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
57  /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
58  	mrc p15, 0, r0, c1, c0, 0   	@ Read CP15 SCTLR Register
59  	bic r0, #CR_V       			@ V = 0
60  	mcr p15, 0, r0, c1, c0, 0   	@ Write CP15 SCTLR Register
61 
62  	/* Set vector address in CP15 VBAR register */
63  	ldr r0, =_start
64  	mcr p15, 0, r0, c12, c0, 0  @Set VBAR
65 #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 cbcmr); 834 periph2 = ((reg & MXC_CCM_CBCMR_PRE_PERIPH2_CLK_SEL_MASK) 835 >> MXC_CCM_CBCMR_PRE_PERIPH2_CLK_SEL_OFFSET); 836 periph1 = ((reg & MXC_CCM_CBCMR_PRE_PERIPH_CLK_SEL_MASK) 837 >> MXC_CCM_CBCMR_PRE_PERIPH_CLK_SEL_OFFSET); 838 839 /* Checking if PLL2 PFD0 or PLL2 PFD2 is using for periph clock */ 840 if ((periph2 != 0x2) && (periph1 != 0x2)) 841 mask528 |= ANATOP_PFD_CLKGATE_MASK(0); 842 843 if ((periph2 != 0x1) && (periph1 != 0x1) && 844 (periph2 != 0x3) && (periph1 != 0x3)) 845 mask528 |= ANATOP_PFD_CLKGATE_MASK(2); 846 847 writel(mask480, &anatop->pfd_480_set); 848 writel(mask528, &anatop->pfd_528_set); 849 writel(mask480, &anatop->pfd_480_clr); 850 writel(mask528, &anatop->pfd_528_clr); 851 }

在第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所示: 在这里插入图片描述

图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代码段

63  /*
64   * entry point of crt0 sequence
65   */
66  
67  ENTRY(_main)
68  
69  /*
70   * Set up initial C runtime environment and call board_init_f(0).
71   */
72  
73  #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
74      ldr sp, =(CONFIG_SPL_STACK)
75  #else
76      ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
77  #endif
78  #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
79      mov r3, sp
80      bic r3, r3, #7
81      mov sp, r3
82  #else
83      bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
84  #endif
85      mov r0, sp
86      bl  board_init_f_alloc_reserve
87      mov sp, r0
88      /* set up gd here, outside any C code */
89      mov r9, r0
90      bl  board_init_f_init_reserve
91  
92      mov r0, #0
93      bl  board_init_f
94  
95  #if ! defined(CONFIG_SPL_BUILD)
96  
97  /*
98   * Set up intermediate environment (new sp and gd) and call
99   * relocate_code(addr_moni). Trick here is that we'll return
100  * 'here' but relocated.
101  */
102 
103     ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
104 #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
105     mov r3, sp
106     bic r3, r3, #7
107     mov sp, r3
108 #else
109     bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
110 #endif
111     ldr r9, [r9, #GD_BD]        /* r9 = gd->bd */
112     sub r9, r9, #GD_SIZE        /* new GD is below bd */
113 
114     adr lr, here
115     ldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off */
116     add lr, lr, r0
117 #if defined(CONFIG_CPU_V7M)
118     orr lr, #1              /* As required by Thumb-only */
119 #endif
120     ldr r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr */
121     b   relocate_code
122 here:
123 /*
124  * now relocate vectors
125  */
126 
127     bl  relocate_vectors
128 
129 /* Set up final (full) environment */
130 
131     bl  c_runtime_cpu_setup /* we still call old routine here */
132 #endif
133 #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
134 # ifdef CONFIG_SPL_BUILD
135     /* Use a DRAM stack for the rest of SPL, if requested */
136     bl  spl_relocate_stack_gd
137     cmp r0, #0
138     movne   sp, r0
139     movne   r9, r0
140 # endif
141     ldr r0, =__bss_start    /* this is auto-relocated! */
142 
143 #ifdef CONFIG_USE_ARCH_MEMSET
144     ldr r3, =__bss_end      /* this is auto-relocated! */
145     mov r1, #0x00000000     /* prepare zero to clear BSS */
146 
147     subs    r2, r3, r0      /* r2 = memset len */
148     bl  memset
149 #else
150     ldr r1, =__bss_end      /* this is auto-relocated! */
151     mov r2, #0x00000000     /* prepare zero to clear BSS */
152 
153 clbss_l:cmp r0, r1          /* while not at end of BSS */
154 #if defined(CONFIG_CPU_V7M)
155     itt lo
156 #endif
157     strlo   r2, [r0]        /* clear 32-bit BSS word */
158     addlo   r0, r0, #4      /* move to next */
159     blo clbss_l
160 #endif
161 
162 #if ! defined(CONFIG_SPL_BUILD)
163     bl coloured_LED_init
164     bl red_led_on
165 #endif
166     /* call board_init_r(gd_t *id, ulong dest_addr) */
167     mov     r0, r9                  /* gd_t */
168     ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
169     /* call board_init_r */
170 #if defined(CONFIG_SYS_THUMB_BUILD)
171     ldr lr, =board_init_r   /* this is auto-relocated! */
172     bx  lr
173 #else
174     ldr pc, =board_init_r   /* this is auto-relocated! */
175 #endif
176     /* we should not return here. */
177 #endif
178 
179 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代码段

56 ulong board_init_f_alloc_reserve(ulong top)
57 {
58  	/* Reserve early malloc arena */
59 		#if defined(CONFIG_SYS_MALLOC_F)
60 	 		top -= CONFIG_SYS_MALLOC_F_LEN;
61 		#endif
62  	/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
63  	top = rounddown(top-sizeof(struct global_data), 16);
64 
65  	return top;
66 }

函数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所示: 在这里插入图片描述

图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所示宏定义: 在这里插入图片描述

图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代码段

27  typedef struct global_data {
28      bd_t *bd;
29      unsigned long flags;
30      unsigned int baudrate;
31      unsigned long cpu_clk;  /* CPU clock in Hz!     */
32      unsigned long bus_clk;
33      /* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
34      unsigned long pci_clk;
35      unsigned long mem_clk;
36  #if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
37      unsigned long fb_base;  /* Base address of framebuffer mem */
38  #endif
......
121 #ifdef CONFIG_DM_VIDEO
122     ulong video_top;        /* Top of video frame buffer area */
123     ulong video_bottom;     /* Bottom of video frame buffer area */
124 #endif
125 } 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代码段

110 void board_init_f_init_reserve(ulong base)
111 {
112     struct global_data *gd_ptr;
113 #ifndef _USE_MEMCPY
114     int *ptr;
115 #endif
116 
117     /*
118      * clear GD entirely and set it up.
119      * Use gd_ptr, as gd may not be properly set yet.
120      */
121 
122     gd_ptr = (struct global_data *)base;
123     /* zero the area */
124 #ifdef _USE_MEMCPY
125     memset(gd_ptr, '\0', sizeof(*gd));
126 #else
127     for (ptr = (int *)gd_ptr; ptr malloc_base = base;
145     /* next alloc will be higher by one 'early malloc arena' size */
146     base += CONFIG_SYS_MALLOC_F_LEN;
147 #endif
148 }

可以看出,此函数用于初始化gd,其实就是清零处理。另外,此函数还设置了gd->malloc_base为gd基地址+gd大小=0X0091FA00+248=0X0091FAF8,在做16字节对齐,最终gd->malloc_base=0X0091FB00,这个也就是early malloc的起始地址。 继续回到示例代码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中稍后会详细分析此函数。 继续回到示例代码32.2.4.1第127行,调用函数relocate_vectors,对中断向量表做重定位,此函数定义在文件arch/arm/lib/relocate.S中,稍后会详细分析此函数。 继续回到示例代码32.2.4.1第131行,调用函数c_runtime_cpu_setup,此函数定义在文件arch/arm/cpu/armv7/start.S中,函数内容如下: 示例代码32.2.4.5 start.S代码段

77 ENTRY(c_runtime_cpu_setup)
78 /*
79  * If I-cache is enabled invalidate it
80  */
81 #ifndef CONFIG_SYS_ICACHE_OFF
82  	mcr p15, 0, r0, c7, c5, 0   @ invalidate icache
83 	 	mcr     p15, 0, r0, c7, c10, 4  @ DSB
84  	mcr     p15, 0, r0, c7, c5, 4   @ ISB
85 #endif
86 
87  	bx  lr
88 
89 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腾出空间,防止Linux kernel覆盖掉uboot,将DRAM前面的区域完整的空出来。在拷贝之前肯定要给uboot各部分分配好内存位置和大小,比如gd应该存放到哪个位置,malloc内存池应该存放到哪个位置等等。这些信息都保存在gd的成员变量中,因此要对gd的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位uboot的时候就会用到这个内存“分配图”。 此函数定义在文件common/board_f.c中定义,代码如下: 示例代码32.2.5.1 board_f.c代码段

1035 void board_init_f(ulong boot_flags)
1036 {
1037 #ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA
1038    /*
1039     * For some archtectures, global data is initialized and used 
1040     * before  calling this function. The data should be preserved. 
1041     * For others, CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined 
1042     * and use the stack here to host global data until relocation.
1043     */
1044    gd_t data;
1045 
1046    gd = &data;
1047 
1048    /*
1049     * Clear global data before it is accessed at debug print
1050     * in initcall_run_list. Otherwise the debug print probably
1051     * get the wrong vaule of gd->have_console.
1052     */
1053    zero_global_data();
1054 #endif
1055 
1056    gd->flags = boot_flags;
1057    gd->have_console = 0;
1058 
1059    if (initcall_run_list(init_sequence_f))
1060        hang();
1061 
1062 #if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
1063        !defined(CONFIG_EFI_APP)
1064    /* NOTREACHED - jump_to_copy() does not return */
1065    hang();
1066 #endif
1067 }
因为没有定义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***************/
1  static init_fnc_t init_sequence_f[] = {
2  		setup_mon_len,                                          
3   	initf_malloc,                                               
4   	initf_console_record,                                       
5   	arch_cpu_init,      	/* basic arch cpu dependent setup 	*/            
6   	initf_dm,                                                   
7   	arch_cpu_init_dm,                                           
8   	mark_bootstage,     	/* need timer, go after init dm 	*/          
9   	board_early_init_f,                                         
10  	timer_init,     		/* initialize timer 					*/                      
11  	board_postclk_init,                                         
12  	get_clocks,                                             
13  	env_init,       		/* initialize environment 			*/                        
14  	init_baud_rate,     	/* initialze baudrate settings 		*/           
15  	serial_init,        	/* serial communications setup 		*/           
16  	console_init_f,     	/* stage 1 init of console 			*/                   
17  	display_options,    	/* say that we are here 				*/                  
18  	display_text_info, 	/* show debugging info if required 	*/           
19  	print_cpuinfo,      	/* display cpu info (and speed) 	*/          
20  	show_board_info,                                            
21  	INIT_FUNC_WATCHDOG_INIT                             
22  	INIT_FUNC_WATCHDOG_RESET                                
23  	init_func_i2c,                                              
24  	announce_dram_init,                                     
25  	/* TODO: unify all these dram functions? */
26  	dram_init,      			/* configure available RAM banks */         
27  	post_init_f,                                                
28  	INIT_FUNC_WATCHDOG_RESET
29  	testdram,                                               
30  	INIT_FUNC_WATCHDOG_RESET
31  	INIT_FUNC_WATCHDOG_RESET
32  	/*
33   	* Now that we have DRAM mapped and working, we can
34   	* relocate the code and continue running from DRAM.
35   	*
36   	* Reserve memory at end of RAM for (top down in that order):
37   	*  - area that won't get touched by U-Boot and Linux (optional)
38   	*  - kernel log buffer
39   	*  - protected RAM
40   	*  - LCD framebuffer
41   	*  - monitor code
42   	*  - board info struct
43  	*/
44  	setup_dest_addr,                                            
45  	reserve_round_4k,                                           
46  	reserve_mmu,                                                
47  	reserve_trace,                                              
48  	reserve_uboot,                                          
49  	reserve_malloc,                                         
50  	reserve_board,                                          
51  	setup_machine,                                          
52  	reserve_global_data,                                        
53  	reserve_fdt,                                                
54  	reserve_arch,                                               
55  	reserve_stacks,                                         
56  	setup_dram_config,                                      
57  	show_dram_config,                                   
58  	display_new_sp,                                     
59  	INIT_FUNC_WATCHDOG_RESET
60  	reloc_fdt,                                                  
61  	setup_reloc,                                            
62  	NULL,
63 };

接下来分析以上函数执行完以后的结果: 第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函数。 第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 and ARMv7-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所示: 在这里插入图片描述

图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所示: 在这里插入图片描述

图32.2.5.2 文本信息 第19行,print_cpuinfo函数用于打印CPU信息,结果如图32.2.5.3所示: 在这里插入图片描述

图32.2.5.3 CPU信息 第20行,show_board_info函数用于打印板子信息,会调用checkboard函数,结果如图32.2.5.4所示: 在这里插入图片描述

图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所示信息: 在这里插入图片描述

图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所示内容: 在这里插入图片描述

图32.2.5.6 添加print函数打印成员变量值 设置好以后重新编译uboot,然后烧写到SD卡中,选择SD卡启动,重启开发板,打开SecureCRT,uboot会输出如图32.2.5.7所示信息: 在这里插入图片描述

图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所示: 在这里插入图片描述

图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所示: 在这里插入图片描述

图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所示: 在这里插入图片描述

图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所示:

图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所示: 在这里插入图片描述

图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所示: 在这里插入图片描述

图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所示: 在这里插入图片描述

图32.2.5.13 信息输出 从图32.2.5.13可以看出,DRAM的起始地址为0X80000000,大小为0X20000000(512MB)。 第57行,show_dram_config函数,用于显示DRAM的配置,如图32.2.5.14所示: 在这里插入图片描述

图32.2.5.14 信息输出 第58行,display_new_sp函数,显示新的sp位置,也就是gd->start_addr_sp,不过要定义宏DEBUG,结果如图32.2.5.15所示: 在这里插入图片描述

图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所示: 在这里插入图片描述 在这里插入图片描述

图32.2.5.16 信息输出 从图32.2.5.16可以看出,uboot重定位后的偏移为0X18747000,重定位后的新地址为0X9FF4700,新的gd首地址为0X9EF44EB8,最终的sp为0X9EF44E90。 至此,board_init_f函数就执行完成了,最终的内存分配如图32.2.5.16所示: 在这里插入图片描述

图32.2.5.16 最终的内存分配图 32.2.6 relocate_code函数详解 relocate_code函数是用于代码拷贝的,此函数定义在文件arch/arm/lib/relocate.S中,代码如下: 示例代码32.2.6.1 relocate.S代码段

  /*
   * void relocate_code(addr_moni)
   *
   * This function relocates the monitor code.
   *
   * NOTE:
   * To prevent the code below from containing references with an 
   * R_ARM_ABS32 relocation record type, we never refer to linker-
   * defined symbols directly. Instead, we declare literals which 
   * contain their relative location with respect to relocate_code, 
   * and at run time, add relocate_code back to them.
*/
  
79  ENTRY(relocate_code)
80      ldr r1, =__image_copy_start /* r1 = 0) {
243         if (tstc()) {   /* we got a key press   */
244             (void) getc();  /* consume input    */
245             puts("\b\b\b 0");
246             abort = 1;  /* don't auto boot  */
247         }
248     }
249 #endif
250 
251     while ((bootdelay > 0) && (!abort)) {
252         --bootdelay;
253         /* delay 1000 ms */
254         ts = get_timer(0);
255         do {
256             if (tstc()) {   /* we got a key press   */
257                 abort  = 1; /* don't auto boot  */
258                 bootdelay = 0;  /* no more delay    */
259 # ifdef CONFIG_MENUKEY
260                 menukey = getc();
261 # else
262                 (void) getc();  /* consume input    */
263 # endif
264                 break;
265             }
266             udelay(10000);
267         } while (!abort && get_timer(ts) flags &= ~GD_FLG_SILENT;
277 #endif
278 
279     return abort;
280 }
函数abortboot_normal同样很多条件编译,删除掉条件编译相关代码后abortboot_normal函数内容如下:

示例代码32.2.9.9 abortboot_normal函数精简

1  static int abortboot_normal(int bootdelay)
2  {
3   	int abort = 0;
4   	unsigned long ts;
5  
6   	if (bootdelay >= 0)
7       		printf("Hit any key to stop autoboot: %2d ", bootdelay);
8  
9  	 	while ((bootdelay > 0) && (!abort)) {
10      	--bootdelay;
11      	/* delay 1000 ms */
12      	ts = get_timer(0);
13      	do {
14          		if (tstc()) {   /* we got a key press   */
15              		abort  = 1; /* don't auto boot  */
16              		bootdelay = 0;  /* no more delay    */
17              		(void) getc();  /* consume input    */
18              		break;
19          		}
20          		udelay(10000);
21      	} while (!abort && get_timer(ts) peek != static_peek || b_peek(inp)));
23  return 0;
24 }
第7~21行中的do-while循环就是处理输入命令的。
第9行调用函数parse_stream进行命令解析。
第14行调用调用run_list函数来执行解析出来的命令。
函数run_list会经过一系列的函数调用,最终通过调用cmd_process函数来处理命令,过程如下:

示例代码32.2.10.4 run_list执行流程

1  static int run_list(struct pipe *pi)
2  {
3   	int rcode=0;
4  
5   	rcode = run_list_real(pi);
6   	......
7   	return rcode;
8  }
9  
10 static int run_list_real(struct pipe *pi)
11 {
12 	 	char *save_name = NULL;
13  	......
14  	int if_code=0, next_if_code=0;  
15  	......
16  	rcode = run_pipe_real(pi);
17  	......
18  	return rcode;
19 }
20 
21 static int run_pipe_real(struct pipe *pi)
22 {
23  	int i;
24 
25  	int nextin;
26  	int flag = do_repeat ? CMD_FLAG_REPEAT : 0;
27  	struct child_prog *child;
28  	char *p;
29  	......
30  	if (pi->num_progs == 1) child = & (pi->progs[0]);
31      	......
32      	return rcode;
33  	} else if (pi->num_progs == 1 && pi->progs[0].argv != NULL) {
34      	......
35      	/* Process the command */
36      	return cmd_process(flag, child->argc, child->argv,
37                 	&flag_repeat, NULL);
38  	}
39 
40  	return -1;
41 }
第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.11 cmd_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.2 U_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.3 ll_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.4 U_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/TFTP protocol”, “[loadAddress] [[hostIPaddr:]bootfilename]” ); 将其展开,结果如下: 示例代码32.2.11.7 dhcp命令展开 U_BOOT_CMD( dhcp, 3, 1, do_dhcp, “boot image via network using DHCP/TFTP protocol”, “[loadAddress] [[hostIPaddr:]bootfilename]” );

1、将U_BOOT_CMD展开后为: U_BOOT_CMD_COMPLETE(dhcp, 3, 1, do_dhcp, “boot image via network using DHCP/TFTP protocol”, “[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/TFTP protocol”, “[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/TFTP protocol”, “[loadAddress] [[hostIPaddr:]bootfilename]”,\ NULL} 从示例代码32.2.11.7可以看出,dhcp命令最终展开结果为: 示例代码32.2.11.8 dhcp命令最终结果

1 cmd_tbl_t _u_boot_list_2_cmd_2_dhcp __aligned(4)       \
2          __attribute__((unused,section(.u_boot_list_2_cmd_2_dhcp))) \              
3         { "dhcp", 3, 1, do_dhcp,  \
4         "boot image via network using DHCP/TFTP protocol", \
5         "[loadAddress] [[hostIPaddr:]bootfilename]",\ 
6         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所示:

在这里插入图片描述

图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结构体

30 struct cmd_tbl_s {
31  	char  	*name;    		/* Command Name         				*/
32  	int     maxargs;   	/* maximum number of arguments  	*/
33  	int     repeatable; 	/* autorepeat allowed?      			*/
34     	/* Implementation function  */
35  	int     (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
36  	char        *usage;	/* Usage message    (short) 			*/
37 #ifdef   CONFIG_SYS_LONGHELP
38  	char        *help;   	/* Help  message    (long)  			*/
39 #endif
40 #ifdef CONFIG_AUTO_COMPLETE
41  							/* do auto completion on the arguments */
42  	int     (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
43 #endif
44 };
45 
46 typedef struct cmd_tbl_s cmd_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 image via 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文件代码段

500 enum command_ret_t cmd_process(int flag, int argc, 
501                 char * const argv[],int *repeatable, ulong *ticks)
502 {
503     enum command_ret_t rc = CMD_RET_SUCCESS;
504     cmd_tbl_t *cmdtp;
505 
506     /* Look up command in command table */
507     cmdtp = find_cmd(argv[0]);
508     if (cmdtp == NULL) {
509         printf("Unknown command '%s' - try 'help'\n", argv[0]);
510         return 1;
511     }
512 
513     /* found - check max args */
514     if (argc > cmdtp->maxargs)
515         rc = CMD_RET_USAGE;
516 
517 #if defined(CONFIG_CMD_BOOTD)
518     /* avoid "bootd" recursion */
519     else if (cmdtp->cmd == do_bootd) {
520         if (flag & CMD_FLAG_BOOTD) {
521             puts("'bootd' recursion detected\n");
522             rc = CMD_RET_FAILURE;
523         } else {
524             flag |= CMD_FLAG_BOOTD;
525         }
526     }
527 #endif
528 
529     /* If OK so far, then do the command */
530     if (!rc) {
531         if (ticks)
532             *ticks = get_timer(0);
533         rc = cmd_call(cmdtp, flag, argc, argv);
534         if (ticks)
535             *ticks = get_timer(*ticks);
536         *repeatable &= cmdtp->repeatable;
537     }
538     if (rc == CMD_RET_USAGE)
539         rc = cmd_usage(cmdtp);
540     return rc;
541 }
第507行,调用函数find_cmd在命令表中找到指定的命令,find_cmd函数内容如下:

示例代码32.2.11.10 command.c文件代码段

118 cmd_tbl_t *find_cmd(const char *cmd)
119 {
120     cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
121     const int len = ll_entry_count(cmd_tbl_t, cmd);
122     return find_cmd_tbl(cmd, start, len);
123 }
参数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文件代码段

490 static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
491 {
492     int result;
493 
494     result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
495     if (result)
496         debug("Command failed, result=%d\n", result);
497     return result;
498 }

在前面的分析中我们知道,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全局变量 43 bootm_headers_t images; /* pointers to os/initrd/fdt images */ images是bootm_headers_t类型的全局变量,bootm_headers_t是个boot头结构体,在文件include/image.h中的定义如下(删除了一些条件编译代码): 示例代码32.3.1.2 bootm_headers_t结构体

304 typedef struct bootm_headers {
305     /*
306      * Legacy os image header, if it is a multi component image
307      * then boot_get_ramdisk() and get_fdt() will attempt to get
308      * data from second and third component accordingly.
309      */
310     image_header_t  *legacy_hdr_os;    	 /* image header pointer */
311     image_header_t  legacy_hdr_os_copy; /* header copy */
312     ulong       legacy_hdr_valid;
313 
......
333 
334 #ifndef USE_HOSTCC
335     image_info_t os;                	/* OS镜像信息   			*/
336     ulong        ep;                	/* OS入口点  				*/
337 
338     ulong       rd_start, rd_end;   	/* ramdisk开始和结束位置	*/
339 
340     char        *ft_addr;           	/* 设备树地址 				*/
341     ulong       ft_len;             	/* 设备树长度 				*/
342 
343     ulong       initrd_start;       	/* initrd开始位置 			*/    
344     ulong       initrd_end;         	/* initrd结束位置 			*/
345     ulong       cmdline_start;      	/* cmdline开始位置 		*/
346     ulong       cmdline_end;        	/* cmdline结束位置 		*/
347     bd_t        *kbd;
348 #endif
349 
350     int     verify;     /* getenv("verify")[0] != 'n' */
351 
352 #define BOOTM_STATE_START   	(0x00000001)
353 #define BOOTM_STATE_FINDOS  	(0x00000002)
354 #define BOOTM_STATE_FINDOTHER 	(0x00000004)
355 #define BOOTM_STATE_LOADOS  	(0x00000008)
356 #define BOOTM_STATE_RAMDISK 	(0x00000010)
357 #define BOOTM_STATE_FDT     		(0x00000020)
358 #define BOOTM_STATE_OS_CMDLINE	(0x00000040)
359 #define BOOTM_STATE_OS_BD_T 	(0x00000080)
360 #define BOOTM_STATE_OS_PREP 	(0x00000100)
361 #define BOOTM_STATE_OS_FAKE_GO	(0x00000200)/*'Almost' run the OS*/
362 #define BOOTM_STATE_OS_GO   	(0x00000400)
363     int     state;
364 
365 #ifdef CONFIG_LMB
366     struct lmb  lmb;    /* 内存管理相关,不深入研究 */
367 #endif
368 } 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结构体

292 typedef struct image_info {
293     ulong	start, end;     			/* blob开始和结束位置*/
294     ulong 	image_start, image_len; 	/* 镜像起始地址(包括blob)和长度 */
295     ulong 	load;          	 			/* 系统镜像加载地址*/
296     uint8_t	comp, type, os;     	/* 镜像压缩、类型,OS类型 */
297     uint8_t   	arch;    				/* CPU架构 */
298 } image_info_t;
全局变量images会在bootz命令的执行中频繁使用到,相当于Linux内核启动的“灵魂”。

32.3.2 do_bootz函数 bootz命令的执行函数为do_bootz,在文件cmd/bootm.c中有如下定义: 示例代码32.3.2.1 do_bootz函数

622 int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
623 {
624     int ret;
625 
626     /* Consume 'bootz' */
627     argc--; argv++;
628 
629     if (bootz_start(cmdtp, flag, argc, argv, &images))
630         return 1;
631 
632     /*
633      * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
634      * disable interrupts ourselves
635      */
636     bootm_disable_interrupts();
637 
638     images.os.os = IH_OS_LINUX;
639     ret = do_bootm_states(cmdtp, flag, argc, argv,
640                   BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
641                   BOOTM_STATE_OS_GO,
642                   &images, 1);
643 
644     return ret;
645 }
第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函数

578 static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
579             char * const argv[], bootm_headers_t *images)
580 {
581     int ret;
582     ulong zi_start, zi_end;
583 
584     ret = do_bootm_states(cmdtp, flag, argc, argv, 
585            					BOOTM_STATE_START,  images, 1);
586 
587     /* Setup Linux kernel zImage entry point */
588     if (!argc) {
589         images->ep = load_addr;
590         debug("*  kernel: default image load address = 0x%08lx\n",
591                 load_addr);
592     } else {
593         images->ep = simple_strtoul(argv[0], NULL, 16);
594         debug("*  kernel: cmdline image address = 0x%08lx\n",
595             images->ep);
596     }
597 
598     ret = bootz_setup(images->ep, &zi_start, &zi_end);
599     if (ret != 0)
600         return 1;
601 
602     lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
603 
604     /*
605      * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
606      * have a header that provide this informaiton.
607      */
608     if (bootm_find_images(flag, argc, argv))
609         return 1;
610 
......
619     return 0;
620 }
第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函数

370 #define LINUX_ARM_ZIMAGE_MAGIC  0x016f2818
371 
372 int bootz_setup(ulong image, ulong *start, ulong *end)
373 {
374     struct zimage_header *zi;
375 
376     zi = (struct zimage_header *)map_sysmem(image, 0);
377     if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) {
378         puts("Bad Linux ARM zImage magic!\n");
379         return 1;
380     }
381 
382     *start = zi->zi_start;
383     *end = zi->zi_end;
384 
385     printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n", image, 
386        		*start, *end);
387 
388     return 0;
389 }
第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所示: 在这里插入图片描述

图32.3.3.1 启动出错 第382、383行初始化函数bootz_setup的参数start和end。 第385行,打印启动信息,如果Linux系统镜像正常的话就会输出图32.3.3.2所示的信息: 在这里插入图片描述

图32.3.3.3 Linux镜像信息 接下来看一下函数bootm_find_images,此函数定义在文件common/bootm.c中,函数内容如下: 示例代码32.3.3.3 bootm_find_images函数

225 int bootm_find_images(int flag, int argc, char * const argv[])
226 {
227     int ret;
228 
229     /* find ramdisk */
230     ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
231                    &images.rd_start, &images.rd_end);
232     if (ret) {
233         puts("Ramdisk image is corrupt or invalid\n");
234         return 1;
235     }
236 
237 #if defined(CONFIG_OF_LIBFDT)
238     /* find flattened device tree */
239     ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,
240                &images.ft_addr, &images.ft_len);
241     if (ret) {
242         puts("Could not find a valid device tree\n");
243         return 1;
244     }
245     set_working_fdt_addr((ulong)images.ft_addr);
246 #endif
......
258     return 0;
259 }
第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函数

591 int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
592             int states, bootm_headers_t *images, int boot_progress)
593 {
594     boot_os_fn *boot_fn;
595     ulong iflag = 0;
596     int ret = 0, need_boot_fn;
597 
598     images->state |= states;
599 
600     /*
601      * Work through the states and see how far we get. We stop on
602      * any error.
603      */
604     if (states & BOOTM_STATE_START)
605         ret = bootm_start(cmdtp, flag, argc, argv);
606 
607     if (!ret && (states & BOOTM_STATE_FINDOS))
608         ret = bootm_find_os(cmdtp, flag, argc, argv);
609 
610     if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
611         ret = bootm_find_other(cmdtp, flag, argc, argv);
612         argc = 0;   /* consume the args */
613     }
614 
615     /* Load the OS */
616     if (!ret && (states & BOOTM_STATE_LOADOS)) {
617         ulong load_end;
618 
619         iflag = bootm_disable_interrupts();
620         ret = bootm_load_os(images, &load_end, 0);
621         if (ret == 0)
622             lmb_reserve(&images->lmb, images->os.load,
623                     (load_end - images->os.load));
624         else if (ret && ret != BOOTM_ERR_OVERLAP)
625             goto err;
626         else if (ret == BOOTM_ERR_OVERLAP)
627             ret = 0;
628 #if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
629         if (images->os.os == IH_OS_LINUX)
630             fixup_silent_linux();
631 #endif
632     }
633 
634     /* Relocate the ramdisk */
635 #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
636     if (!ret && (states & BOOTM_STATE_RAMDISK)) {
637         ulong rd_len = images->rd_end - images->rd_start;
638 
639         ret = boot_ramdisk_high(&images->lmb, images->rd_start,
640             rd_len, &images->initrd_start, &images->initrd_end);
641         if (!ret) {
642             setenv_hex("initrd_start", images->initrd_start);
643             setenv_hex("initrd_end", images->initrd_end);
644         }
645     }
646 #endif
647 #if defined(CONFIG_OF_LIBFDT) && defined(CONFIG_LMB)
648     if (!ret && (states & BOOTM_STATE_FDT)) {
649         boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
650         ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
651                     &images->ft_len);
652     }
653 #endif
654 
655     /* From now on, we need the OS boot function */
656     if (ret)
657         return ret;
658     boot_fn = bootm_os_get_boot_func(images->os.os);
659     need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
660             BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
661             BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
662     if (boot_fn == NULL && need_boot_fn) {
663         if (iflag)
664             enable_interrupts();
665         printf("ERROR: booting os '%s' (%d) is not supported\n",
666                genimg_get_os_name(images->os.os), images->os.os);
667         bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
668         return 1;
669     }
670 
671     /* Call various other states that are not generally used */
672     if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
673         ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
674     if (!ret && (states & BOOTM_STATE_OS_BD_T))
675         ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
676     if (!ret && (states & BOOTM_STATE_OS_PREP))
677         ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
678 
679 #ifdef CONFIG_TRACE
680     /* Pretend to run the OS, then run a user command */
681     if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
682         char *cmd_list = getenv("fakegocmd");
683 
684         ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
685                 images, boot_fn);
686         if (!ret && cmd_list)
687             ret = run_command_list(cmd_list, -1, flag);
688     }
689 #endif
690 
691     /* Check for unsupported subcommand. */
692     if (ret) {
693         puts("subcommand not supported\n");
694         return ret;
695     }
696 
697     /* Now run the OS! We hope this doesn't return */
698     if (!ret && (states & BOOTM_STATE_OS_GO))
699         ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
700                 images, boot_fn);
......
712     return ret;
713 }
函数do_bootm_states根据不同的BOOT状态执行不同的代码段,通过如下代码来判断BOOT状态:

states & BOOTM_STATE_XXX 在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函数

591 int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
592             int states, bootm_headers_t *images, int boot_progress)
593 {
594     boot_os_fn *boot_fn;
595     ulong iflag = 0;
596     int ret = 0, need_boot_fn;
597 
598     images->state |= states;
599 
600     /*
601      * Work through the states and see how far we get. We stop on
602      * any error.
603      */
604     if (states & BOOTM_STATE_START)
605         ret = bootm_start(cmdtp, flag, argc, argv);
......
654 
655     /* From now on, we need the OS boot function */
656     if (ret)
657         return ret;
658     boot_fn = bootm_os_get_boot_func(images->os.os);
659     need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
660             BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
661             BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
662     if (boot_fn == NULL && need_boot_fn) {
663         if (iflag)
664             enable_interrupts();
665         printf("ERROR: booting os '%s' (%d) is not supported\n",
666                genimg_get_os_name(images->os.os), images->os.os);
667         bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
668         return 1;
669     }
670 
......
676     if (!ret && (states & BOOTM_STATE_OS_PREP))
677         ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
678 
679 #ifdef CONFIG_TRACE
680     /* Pretend to run the OS, then run a user command */
681     if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
682         char *cmd_list = getenv("fakegocmd");
683 
684         ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
685                 images, boot_fn);
686         if (!ret && cmd_list)
687             ret = run_command_list(cmd_list, -1, flag);
688     }
689 #endif
690 
691     /* Check for unsupported subcommand. */
692     if (ret) {
693         puts("subcommand not supported\n");
694         return ret;
695     }
696 
697     /* Now run the OS! We hope this doesn't return */
698     if (!ret && (states & BOOTM_STATE_OS_GO))
699         ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
700                 images, boot_fn);
......
712     return ret;
713 }
第604、605行,处理BOOTM_STATE_START阶段,bootz_start会执行这一段代码,这里调用函数bootm_start,此函数定义在文件common/bootm.c中,函数内容如下:

示例代码32.3.4.2 bootm_start函数 69 static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, 70 char * const argv[]) 71 { 72 memset((void )&images, 0, sizeof(images)); / 清空images / 73 images.verify = getenv_yesno(“verify”);/ 初始化verfify成员 / 74 75 boot_start_lmb(&images); 76 77 bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, “bootm_start”); 78 images.state = BOOTM_STATE_START;/ 设置状态为BOOTM_STATE_START */ 79 80 return 0; 81 } 接着回到示例代码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保存着传递给Linux kernel的参数。 第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.3 boot_selected_os函数

476 int boot_selected_os(int argc, char * const argv[], int state,
477              bootm_headers_t *images, boot_os_fn *boot_fn)
478 {
479     arch_preboot_os();
480     boot_fn(state, argc, argv, images);
......
490     return BOOTM_ERR_RESET;
491 }
第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.1 bootm_os_get_boot_func函数

493 boot_os_fn *bootm_os_get_boot_func(int os)
494 {
495 #ifdef CONFIG_NEEDS_MANUAL_RELOC
496     static bool relocated;
497 
498     if (!relocated) {
499         int i;
500 
501         /* relocate boot function table */
502         for (i = 0; i reloc_off;
505 
506         relocated = true;
507     }
508 #endif
509     return boot_os[os];
510 }
第495~508行是条件编译,在本uboot中没有用到,因此这段代码无效,只有509行有效。在509行中boot_os是个数组,这个数组里面存放着不同的系统对应的启动函数。boot_os也定义在文件common/bootm_os.c中,如下所示:

示例代码32.3.5.2 boot_os数组

435 static boot_os_fn *boot_os[] = {
436     [IH_OS_U_BOOT] = do_bootm_standalone,
437 #ifdef CONFIG_BOOTM_LINUX
438     [IH_OS_LINUX] = do_bootm_linux,
439 #endif
......
465 #ifdef CONFIG_BOOTM_OPENRTOS
466     [IH_OS_OPENRTOS] = do_bootm_openrtos,
467 #endif
468 };
第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函数

339 int do_bootm_linux(int flag, int argc, char * const argv[],
340            bootm_headers_t *images)
341 {
342     /* No need for those on ARM */
343     if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
344         return -1;
345 
346     if (flag & BOOTM_STATE_OS_PREP) {
347         boot_prep_linux(images);
348         return 0;
349     }
350 
351     if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
352         boot_jump_linux(images, flag);
353         return 0;
354     }
355 
356     boot_prep_linux(images);
357     boot_jump_linux(images, flag);
358     return 0;
359 }
第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函数

272 static void boot_jump_linux(bootm_headers_t *images, int flag)
273 {
274 #ifdef CONFIG_ARM64
......
292 #else
293     unsigned long machid = gd->bd->bi_arch_number;
294     char *s;
295     void (*kernel_entry)(int zero, int arch, uint params);
296     unsigned long r2;
297     int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
298 
299     kernel_entry = (void (*)(int, int, uint))images->ep;
300 
301     s = getenv("machid");
302     if (s) {
303         if (strict_strtoul(s, 16, &machid) ft_len)
316         r2 = (unsigned long)images->ft_addr;
317     else
318         r2 = gd->bd->bi_boot_params;
319 
......
328             kernel_entry(0, machid, r2);
329     }
330 #endif
331 }
第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.3 announce_and_cleanup函数

72 static void announce_and_cleanup(int fake)
73 {
74  	printf("\nStarting kernel ...%s\n\n", fake ?
75      	"(fake run for tracing)" : "");
76  	bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF, "start_kernel");
......
87  	cleanup_before_linux();
88 }
第74行,在启动Linux之前输出“Starting kernel ...”信息,如图32.3.6.1所示:

在这里插入图片描述

图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所示: 在这里插入图片描述

图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这条道路上,有众多的学习者陪着你,大家相互搀扶,终能踏出一条康庄大道,祝所有的同学终有所获!

关注
打赏
1665308814
查看更多评论
立即登录/注册

微信扫码登录

0.0502s