u盘启动盘linux66 (u盘启动盘linux)

1)实验平台:正点原子Linux开发板

2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南

关注官方微信号公众号,获取更多资料:正点原子


上一章我们详细的分析了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以后才会在根目录下出现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函数。


电脑知识