1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》
关注官方微信号公众号,获取更多资料:正点原子
上一章我们详细的分析了uboot的顶层Makefile,理清了uboot的编译流程。本章我们来详细的分析一下uboot的启动流程,理清uboot是如何启动的。通过对uboot启动流程的梳理,我们就可以掌握一些外设是在哪里被初始化的,这样当我们需要修改这些外设驱动的时候就会心里有数。另外,通过分析uboot的启动流程可以了解Linux内核是如何被启动的。
要分析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以后才会在根目录下出现u-boot.lds文件!
只有编译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.1可以看出,_start后面就是中断向量表,从图中的“.section ".vectors", "ax”可以得到,此代码存放在.vectors段里面。
第10行,使用如下命令在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。
第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代码段
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 << 13) /* Vectors relocated to 0xffff0000 */
因此这一行的目的就是清除SCTLR寄存器中的bit13,SCTLR寄存器结构如图32.2.1.1所示:
图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代码段
67/* the mask ROM code should have PLL and others stable */
68 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
69 bl cpu_init_cp15
70 bl cpu_init_crit
71 #endif
72
73 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代码段
105/*****************************************************************
106 *
107 * cpu_init_cp15
108 *
109 * Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on
110 * unless CONFIG_SYS_ICACHE_OFF is defined.
111 *
112 *****************************************************************/
113 ENTRY(cpu_init_cp15)
114/*
115 * Invalidate L1 I/D
116 */
117 mov r0, #0 @ set up for MCR
118 mcr p15,0, r0, c8, c7,0 @ invalidate TLBs
119 mcr p15,0, r0, c7, c5,0 @ invalidate icache
120 mcr p15,0, r0, c7, c5,6 @ invalidate BP array
121 mcr p15,0, r0, c7, c10,4 @ DSB
122 mcr p15,0, r0, c7, c5,4 @ ISB
123
124/*
125 * disable MMU stuff and caches
126 */
127 mrc p15,0, r0, c1, c0,0
128 bic r0, r0, #0x00002000 @ clear bits 13(--V-)
129 bic r0, r0, #0x00000007 @ clear bits 2:0(-CAM)
130 orr r0, r0, #0x00000002 @ set bit 1(--A-) Align
131 orr r0, r0, #0x00000800 @ set bit 11(Z---) BTB
132 #ifdef CONFIG_SYS_ICACHE_OFF
133 bic r0, r0, #0x00001000 @ clear bit 12(I) I-cache
134 #else
135 orr r0, r0, #0x00001000 @ set bit 12(I) I-cache
136 #endif
137 mcr p15,0, r0, c1, c0,0
138
......
255
256 mov pc, r5 @ back to my caller
257 ENDPROC(cpu_init_cp15)
函数cpu_init_cp15都是一些和CP15有关的内容,我们不用关心,有兴趣的可以详细的看一下。
函数cpu_init_crit也在是定义在start.S文件中,函数内容如下:
示例代码32.2.1.8 start.S代码段
260/*****************************************************************
261 *
262 * CPU_init_critical registers
263 *
264 * setup important registers
265 * setup memory timing
266 *
267 *****************************************************************/
268 ENTRY(cpu_init_crit)
269/*
270 * Jump to board specific initialization...
271 * The Mask ROM will have already initialized
272 * basic memory. Go here to bump up clock rate and handle
273 * wake up conditions.
274 */
275 b lowlevel_init @ go setup pll,mux,memory
276 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代码段
14 #include <asm-offsets.h>
15 #include <config.h>
16 #include <linux/linkage.h>
17
18 ENTRY(lowlevel_init)
19 /*
20 * Setup a temporary stack. Global data is not available yet.
21 */
22 ldr sp,=CONFIG_SYS_INIT_SP_ADDR
23 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
24 #ifdef CONFIG_SPL_DM
25 mov r9, #0
26 #else
27 /*
28 * Set up global data for boards that still need it. This will be
29 * removed soon.
30 */
31 #ifdef CONFIG_SPL_BUILD
32 ldr r9,=gdata
33 #else
34 sub sp, sp, #GD_SIZE
35 bic sp, sp, #7
36 mov r9, sp
37 #endif
38 #endif
39 /*
40 * Save the old lr(passed in ip) and the current lr to stack
41 */
42 push {ip, lr}
43
44 /*
45 * Call the very early init function. This should do only the
46 * absolute bare minimum to get started. It should not:
47 *
48 * - set up DRAM
49 * - use global_data
50 * - clear BSS
51 * - try to start a console
52 *
53 * For boards with SPL this should be empty since SPL can do all
54 * of this init in the SPL board_init_f() function which is
55 * called immediately after this.
56 */
57 bl s_init
58 pop {ip, pc}
59 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代码段
234 #define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR
235 #define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE
236
237 #define CONFIG_SYS_INIT_SP_OFFSET \
238(CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
239 #define CONFIG_SYS_INIT_SP_ADDR \
240(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代码段
71 #define IRAM_BASE_ADDR 0x00900000
......
408 #if!(defined(CONFIG_MX6SX)|| defined(CONFIG_MX6UL)|| \
409 defined(CONFIG_MX6SLL)|| defined(CONFIG_MX6SL))
410 #define IRAM_SIZE 0x00040000
411 #else
412 #define IRAM_SIZE 0x00020000
413 #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.4 generic-asm-offsets.h代码段
1 #ifndef __GENERIC_ASM_OFFSETS_H__
2 #define __GENERIC_ASM_OFFSETS_H__
3/*
4 * DO NOT MODIFY.
5 *
6 * This file was generated by Kbuild
7 */
8
9 #define GENERATED_GBL_DATA_SIZE 256
10 #define GENERATED_BD_INFO_SIZE 80
11 #define GD_SIZE 248
12 #define GD_BD 0
13 #define GD_MALLOC_BASE 192
14 #define GD_RELOCADDR 48
15 #define GD_RELOC_OFF 68
16 #define GD_START_ADDR_SP 64
17
18 #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,
结果如下图所示:
图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所示:
图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代码段
808void s_init(void)
809{
810struct anatop_regs *anatop =(struct anatop_regs *)ANATOP_BASE_ADDR;
811struct mxc_ccm_reg *ccm =(struct mxc_ccm_reg *)CCM_BASE_ADDR;
812 u32 mask480;
813 u32 mask528;
814 u32 reg, periph1, periph2;
815
816if(is_cpu_type(MXC_CPU_MX6SX)|| is_cpu_type(MXC_CPU_MX6UL)||
817 is_cpu_type(MXC_CPU_MX6ULL)|| is_cpu_type(MXC_CPU_MX6SLL))
818return;
819
820/* Due to hardware limitation, on MX6Q we need to gate/ungate
821 * all PFDs to make sure PFD is working right, otherwise, PFDs
822 * may not output clock after reset, MX6DL and MX6SL have added
823 * 396M pfd workaround in ROM code, as bus clock need it
824 */
825
826 mask480 = ANATOP_PFD_CLKGATE_MASK(0)|
827 ANATOP_PFD_CLKGATE_MASK(1)|
828 ANATOP_PFD_CLKGATE_MASK(2)|
829 ANATOP_PFD_CLKGATE_MASK(3);
830 mask528 = ANATOP_PFD_CLKGATE_MASK(1)|
831 ANATOP_PFD_CLKGATE_MASK(3);
832
833 reg = readl(&ccm->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 */
840if((periph2 !=0x2)&&(periph1 !=0x2))
841 mask528 |= ANATOP_PFD_CLKGATE_MASK(0);
842
843if((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函数。
电脑知识