热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Uboot引导流程

U-Boot的源码目录结构1、board:开发板相关的配置文件,一个子文件对应一个开发板配置;2、common:通用的多功能

U-Boot的源码目录结构
1、board:开发板相关的配置文件,一个子文件对应一个开发板配置;

2、common:通用的多功能函数实现,比如环境,命令,控制台相关函数等;

3、cpu:存放特定CPU结构相关的目录;

4、disk:硬盘接口程序;

5、doc:

6、drivers:支持的各种设备的驱动程序;

7、dtt:数字温度测量器或传感器的驱动;

8、examples:这个不解析;

9、fs:文件系统,最常用的是JFFS2文件系统;

10、include:头文件和开发板配置文件,开发板的配置文件放在include/configs目录下;

11、lib_generic:通用函数库,比如printf函数等;

12、lib_*:某一具体平台架构下的通用函数库,lib_i386就对应i386系统架构的通用函数库;

13、nand_spl:

14、net:与网络协议相关的代码,bootp协议、TFTP协议、NFS文件系统的实现;

15、post:上电自检程序;

16、rtc:实时时钟的驱动;

17、temp:

18、tools:制作s-record、u-boot格式映像的工具,比如mkimage。

U-Boot启动流程(简约)
大多数BootLoader都分为stage1和stage2两大部分,U-boot也不例外。依赖于cpu体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。
1、 stage1(start.s代码结构)
U-boot的stage1代码通常放在start.s文件中,它用汇编语言写成,其主要代码部分如下:
(1) 定义入口。由于一个可执行的image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在rom(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
(2)设置异常向量(exception vector)。
(3)设置CPU的速度、时钟频率及中断控制寄存器。
(4)初始化内存控制器 。
(5)将rom中的程序复制到ram中。
(6)初始化堆栈 。
(7)转到ram中执行,该工作可使用指令ldrpc来完成。
2、 stage2(C语言代码部分)
lib_arm/board.c中的start armboot是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数主要完成如下操作:
(1)调用一系列的初始化函数。
(2)初始化flash设备。
(3)初始化系统内存分配函数。
(4)如果目标系统拥有nand设备,则初始化nand设备。
(5)如果目标系统有显示设备,则初始化该类设备。
(6)初始化相关网络设备,填写ip,c地址等。
(7)进入命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。
下面结合SMDK2410(ARM920T)的U-Boot的源码来分析下U-Boot的整个引导流程。

Stage I过程分析
从文件层面上看主要流程是在这几个文件中进行的:cpu/xxx/start.S、board/xxx/lowlevel_init.S、lib_arm_board.c。

设置中断向量表

cpu/arm920t/start.S开头有如下代码:

/**************************************************************************** Jump vector table as in table 3.1 in [1]***************************************************************************/.globl _start
_start: b reset /* 复位,CPU复位,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 /* irq中断向量 */ldr pc, _fiq /* fiq中断向量 */_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq.balignl 16,0xdeadbeef /* 16字节对齐,不足之处,用0xdeadbeef填充(搞笑,用“死牛”填充) */
红色标记的"undefined_instruction"是一个标号,即地址值,对应的就是发生"未定义指令"中断时,系统处理该中断的代码的地址。

/** exception handlers*/.align 5
undefined_instruction:get_bad_stackbad_save_user_regsbl do_undefined_instruction.align 5
software_interrupt:get_bad_stackbad_save_user_regsbl do_software_interrupt.align 5
prefetch_abort:get_bad_stackbad_save_user_regsbl do_prefetch_abort.align 5
data_abort:get_bad_stackbad_save_user_regsbl do_data_abort.align 5
not_used:get_bad_stackbad_save_user_regsbl do_not_used#ifdef CONFIG_USE_IRQ.align 5
irq:get_irq_stackirq_save_user_regsbl do_irqirq_restore_user_regs.align 5
fiq:get_fiq_stack/* someone ought to write a more effiction fiq_save_user_regs */irq_save_user_regsbl do_fiqirq_restore_user_regs#else.align 5
irq:get_bad_stackbad_save_user_regsbl do_irq.align 5
fiq:get_bad_stackbad_save_user_regsbl do_fiq#endif

在cpu/arm920t/interrupts.c中有对应的do_undefined_instruction方法。

void do_undefined_instruction (struct pt_regs *pt_regs)
{printf ("undefined instruction\n");show_regs (pt_regs);bad_mode ();
}void do_software_interrupt (struct pt_regs *pt_regs)
{printf ("software interrupt\n");show_regs (pt_regs);bad_mode ();
}void do_prefetch_abort (struct pt_regs *pt_regs)
{printf ("prefetch abort\n");show_regs (pt_regs);bad_mode ();
}void do_data_abort (struct pt_regs *pt_regs)
{printf ("data abort\n");show_regs (pt_regs);bad_mode ();
}void do_not_used (struct pt_regs *pt_regs)
{printf ("not used\n");show_regs (pt_regs);bad_mode ();
}void do_fiq (struct pt_regs *pt_regs)
{printf ("fast interrupt request\n");show_regs (pt_regs);bad_mode ();
}void do_irq (struct pt_regs *pt_regs)
{
#if defined (CONFIG_USE_IRQ) && defined (CONFIG_ARCH_INTEGRATOR)/* ASSUMED to be a timer interrupt  *//* Just clear it - count handled in *//* integratorap.c                   */*(volatile ulong *)(CFG_TIMERBASE + 0x0C) = 0;
#elseprintf ("interrupt request\n");show_regs (pt_regs);bad_mode ();
#endif
}

当一个中断发生,CPU首先会在中断向量表中查找对应的中断向量,然后跳转到对应的中断处理程序处执行。

CPU复位操作,会使CPU进入SVC模式。

设置CPU进入SVC模式

ARM处理器有7中模式,由CPSR的[4:0]决定。

用户模式USR:正常程序运行的工作模式。只能读CPSR。

系统模式SYS:与用户模式共用一套寄存器。用于支持操作系统的特权任务模式,它可以直接切换到其他模式。

管理模式SVC:操作系统的特权任务模式。系统复位和软件中断时才能进入该模式。

除了用户模式外,其他模式都是特权模式。只有在特权模式下,才允许对当前的程序状态寄存器的控制位直接进行读写。特权模式中除了系统模式外,都是异常模式。特权模式可以访问所有系统资源。一般,进入特权模式是为了处理中断、异常或者访问被保护的资源。

ARM处理器的工作模式:

处理器模式特权模式说明CPSR[4:0]
用户模式USR用户程序运行模式10000
系统模式SYS运行特权级的操作系统级任务 
管理模式SVC提供操作系统使用的保护模式10011
异常终止模式ABT用于虚拟存储及存储保护10111
未定义模式UND用于支持通过软件仿真硬件的协处理器11011
一般中断模式IRQ用户通常的中断使用10010
快速中断模式FIQ用于高速数据传输和通道处理10001

CPSR,Current Program Status Register,当前程序状态寄存器。

/** the actual reset code*/reset:/** set the cpu to SVC32 mode*/mrs r0,cpsrbic r0,r0,#0x1f /* 位清零,工作模式为清零 */orr r0,r0,#0xd3 /* 逻辑或,工作模式为设为'(110)10011',即设置CPU为SVC模式,并将中断禁止位和快中断禁止位置1(屏蔽)*/msr cpsr,r0

设置控制寄存器,关看门狗,关中断

看门狗是一个硬件模块,当系统出现死机现象,看门狗就会自动重启系统。看门狗的硬件逻辑:其硬件有一个记录超时功能,它要求用户每隔一段时间(根据需求配置)对某个寄存器置位(简称“喂狗”),若超时为进行“喂狗”动作,系统就会重启。

/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000 /* 看门狗寄存器 */
# define INTMSK 0x14400008 /* Interupt-Controller base addresses 中断屏蔽寄存器 */
# define CLKDIVN 0x14800014 /* clock divisor register 时钟分频寄存器 */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)ldr     r0, =pWTCONmov     r1, #0x0 /* 向看门狗寄存器写入0 */str     r1, [r0]

/** mask all IRQs by setting all bits in the INTMR - default*/mov r1, #0xffffffffldr r0, =INTMSK /* 屏蔽所有中断 */str r1, [r0]

INTMSK寄存器是一个32位寄存器,每一位对应一个中断,向其中写入0xffffffff,即将INTMSK寄存器全部置1,从而屏蔽对应的中断。

INTSUBMSK寄存器也是一个32位寄存器,但是只使用了低15位,向其中写入0x7fffffff,即可屏蔽对应的中断。

以上代码完成了对pWTCON、INTMSK、INTSUBMSK、CLKDIVN四个寄存器的地址设置,并且通过向看门狗寄存器写入0,禁止看门狗的复位功能(即重启功能),否则,在U-Boot启动过程中,CPU将不断重启。

初始化CPU时钟

关闭MMU和Cache

MMU,Memory Management Unit,即内存管理单元。MMU是CPU中用来管理虚拟存储器、物理存储器的控制线路,同时负责将虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权。

/** we do sys-critical inits only at reboot,* not when booting from ram!*/
#ifndef CONFIG_SKIP_LOWLEVEL_INITbl cpu_init_crit
#endif
cpu_init_crit到底做了哪些操作呢?

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:/** flush v4 I/D caches*/mov r0, #0mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache 向c7写入0 */mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB 向c8写入0 *//** disable MMU stuff and caches*/mrc p15, 0, r0, c1, c0, 0 /* 读出控制寄存器到r0中 */bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)orr r0, r0, #0x00000002 @ set bit 2 (A) Alignorr r0, r0, #0x00001000 @ set bit 12 (I) I-Cachemcr p15, 0, r0, c1, c0, 0 /* 保存r0到控制寄存器中 *//** before relocating, we have to setup RAM timing* because memory timing is board-dependend, you will* find a lowlevel_init.S in your board directory.*/mov ip, lrbl lowlevel_init /* 跳转到lowlevel_init,执行完lowlevel_init后返回 */mov lr, ipmov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */

注:TLB,Translation Lookaside Buffer,即旁路缓冲,它的作用是在处理器访问内存数据的时候做地址转换。TLB中存放了一些页表文件,文件中记录了虚拟地址和物理地址的映射关系。当程序访问呢一个虚拟地址,会从TLB中查询出对应的物理地址,然后访问物理地址。

代码中的c0、c1、c7、c8是ARM920T的协处理器CP15的寄存器。其中,c7是cache控制寄存器,c8是TLB控制寄存器。

关闭MMU是通过修改CP15协处理器的c1寄存器来实现的。CP15的c1寄存器格式如下:

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

·

·

V

I

·

·

R

S

B

·

·

·

·

C

A

M

对应位的意义如下:

置0

置1

V

异常向量在0x00000000

异常向量在0xFFFF0000

I

关闭ICaches

开启ICaches

R、S

用来与页表中的描述符一起确定内存的访问权限

B

CPU为小字节序

CPU为大字节序

C

关闭DCaches

开启DCaches

A

数据访问时不进行地址对齐检查

数据访问时进行地址对齐检查

M

关闭MMU

开启MMU

初始化内存

lowlevel_init完成了内存初始化的任务,由于内存初始化时依赖开发板的,所以lowlevel_init的代码一般放在board下面相应的目录。对于SMDK2410,lowlevel_init的代码在board/smdk2410/lowlevel_init.S中。

_TEXT_BASE:.word TEXT_BASE.globl lowlevel_init
lowlevel_init:/* memory control configuration *//* make r0 relative the current location so that it *//* reads SMRDATA out of FLASH rather than memory ! */ldr r0, =SMRDATAldr r1, _TEXT_BASEsub r0, r0, r1ldr r1, =BWSCON /* Bus Width Status Controller */add r2, r0, #13*4
0:ldr r3, [r0], #4str r3, [r1], #4cmp r2, r0bne 0b/* everything is fine now */mov pc, lr.ltorg
/* the literal pools origin */

SMRDATA: /* 13个寄存器的值 */.word (0&#43;(B1_BWSCON<<4)&#43;(B2_BWSCON<<8)&#43;(B3_BWSCON<<12)&#43;(B4_BWSCON<<16)&#43;(B5_BWSCON<<20)&#43;(B6_BWSCON<<24)&#43;(B7_BWSCON<<28)).word ((B0_Tacs<<13)&#43;(B0_Tcos<<11)&#43;(B0_Tacc<<8)&#43;(B0_Tcoh<<6)&#43;(B0_Tah<<4)&#43;(B0_Tacp<<2)&#43;(B0_PMC)).word ((B1_Tacs<<13)&#43;(B1_Tcos<<11)&#43;(B1_Tacc<<8)&#43;(B1_Tcoh<<6)&#43;(B1_Tah<<4)&#43;(B1_Tacp<<2)&#43;(B1_PMC)).word ((B2_Tacs<<13)&#43;(B2_Tcos<<11)&#43;(B2_Tacc<<8)&#43;(B2_Tcoh<<6)&#43;(B2_Tah<<4)&#43;(B2_Tacp<<2)&#43;(B2_PMC)).word ((B3_Tacs<<13)&#43;(B3_Tcos<<11)&#43;(B3_Tacc<<8)&#43;(B3_Tcoh<<6)&#43;(B3_Tah<<4)&#43;(B3_Tacp<<2)&#43;(B3_PMC)).word ((B4_Tacs<<13)&#43;(B4_Tcos<<11)&#43;(B4_Tacc<<8)&#43;(B4_Tcoh<<6)&#43;(B4_Tah<<4)&#43;(B4_Tacp<<2)&#43;(B4_PMC)).word ((B5_Tacs<<13)&#43;(B5_Tcos<<11)&#43;(B5_Tacc<<8)&#43;(B5_Tcoh<<6)&#43;(B5_Tah<<4)&#43;(B5_Tacp<<2)&#43;(B5_PMC)).word ((B6_MT<<15)&#43;(B6_Trcd<<2)&#43;(B6_SCAN)).word ((B7_MT<<15)&#43;(B7_Trcd<<2)&#43;(B7_SCAN)).word ((REFEN<<23)&#43;(TREFMD<<22)&#43;(Trp<<20)&#43;(Trc<<18)&#43;(Tchr<<16)&#43;REFCNT).word 0x32.word 0x30.word 0x30

lowlevel_init的作用就是将SMRDATA开始的13个值复制给开始地址[BWSCON]13个寄存器&#xff0c;从而完成了存储控制器的设置。

复制StageII的代码到RAM

上面初始化玩内存之后会跳转回start.S继续执行。

#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM */adr r0, _start /* r0 <- current position of code */ldr r1, _TEXT_BASE /* test if we run from flash or RAM */cmp r0, r1 /* don&#39;t reloc during debug */beq stack_setupldr r2, _armboot_startldr r3, _bss_startsub r2, r3, r2 /* r2 <- size of armboot */add r2, r0, r2 /* r2 <- source end address */copy_loop:ldmia r0!, {r3-r10} /* copy from source address [r0] */stmia r1!, {r3-r10} /* copy to target address [r1] */cmp r0, r2 /* until source end addreee [r2] */ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */

relocate处首先比较_start和_TEXT_BASE的地址&#xff0c;如果相同则说明程序以及在内存中&#xff0c;无须加载。copy_loop处则实现了循环复制Flash的数据到内存中&#xff0c;每次复制8个字长的数据。

设置堆栈

只要将SP指针指向一段未使用的内存就算是完成了对堆栈的设置了。

/* Set up the stack */
stack_setup:ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */sub r0, r0, #CFG_MALLOC_LEN /* malloc area */sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQsub r0, r0, #(CONFIG_STACKSIZE_IRQ&#43;CONFIG_STACKSIZE_FIQ)
#endifsub sp, r0, #12 /* leave 3 words for abort-stack */

根据上面的代码知道U-Boot内存使用情况。


清除BSS段

clear_bss:ldr r0, _bss_start /* find start of bss segment */ldr r1, _bss_end /* stop here */mov r2, #0x00000000 /* clear */clbss_l:str r2, [r0] /* clear loop... */add r0, r0, #4cmp r0, r1ble clbss_l

BSS段中存放有初始值为0得变量、无初始值的全局变量和一些静态变量。清除BSS段&#xff0c;就是将这些变量初始化赋值0&#xff0c;否则这些变量的初始值将是一个随机的值&#xff0c;若有程序直接使用这些值将会引起未知的后果。

跳转到Stage II代码入口

#if 0/* try doing this stuff after the relocation */ldr r0, &#61;pWTCONmov r1, #0x0str r1, [r0]/** mask all IRQs by setting all bits in the INTMR - default*/mov r1, #0xffffffffldr r0, &#61;INTMRstr r1, [r0]/* FCLK:HCLK:PCLK &#61; 1:2:4 *//* default FCLK is 120 MHz ! */ldr r0, &#61;CLKDIVNmov r1, #3str r1, [r0]/* END stuff after relocation */
#endifldr pc, _start_armboot_start_armboot: .word start_armboot

由代码可以看出&#xff0c;Stage II代码的入口处在start_armboot。



推荐阅读
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 微服务下的几个难点问题及常见的解决方案
    原文链接:https:cloud.tencent.comdevelopernews1362051背景介绍1.1幂等性定义数学定义在数学里,幂等有 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • switch语句的一些用法及注意事项
    本文介绍了使用switch语句时的一些用法和注意事项,包括如何实现"fall through"、default语句的作用、在case语句中定义变量时可能出现的问题以及解决方法。同时也提到了C#严格控制switch分支不允许贯穿的规定。通过本文的介绍,读者可以更好地理解和使用switch语句。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 如何搭建Java开发环境并开发WinCE项目
    本文介绍了如何搭建Java开发环境并开发WinCE项目,包括搭建开发环境的步骤和获取SDK的几种方式。同时还解答了一些关于WinCE开发的常见问题。通过阅读本文,您将了解如何使用Java进行嵌入式开发,并能够顺利开发WinCE应用程序。 ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • NFS文件共享系统
    1、概述:NFS(NetworkFileSystem)意为网络文件系统,它最大的功能就是可以通过网络,让不同的机器不同的操作系统可以共享 ... [详细]
  • SOA架构理解理解SOA架构,了解ESB概念,明白SOA与微服务的区别和联系,了解SOA与热门技术的结合与应用。1、面向服务的架构SOASOA(ServiceOrien ... [详细]
author-avatar
蓝雪帝国666
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有