u-boot启动流程分析

u-boot启动流程分析

       以smdk2410为例,分析u-boot的启动流程。u-boot的启动流程是指从cpu上电开机执行u-boot到u-boot成功加载完操作系统的过程。这一过程可以分为两个阶段,各个阶段的功能如下:

第一阶段的功能:

  • 硬件设备初始化;
  • 加载u-boot第二阶段的代码到RAM空间;
  • 设置好栈;
  • 跳转到u-boot第二阶段代码的入口处;

第二阶段的功能:

  • 初始化本阶段使用的硬件设备;
  • 检测系统内存映射;
  • 将Linux内核从Flash读取到RAM中;
  • 为Linux内核设置启动参数;
  • 调用Linux内核。

1、u-boot启动流程的第一阶段代码分析

根据连接器脚本cpu/arm920t/u-boot.lds中指定的链接方式可知,u-boot代码段第一个链接的是cpu/arm920t/start.o,入口标号是_start,因此u-boot的入口代码在对应源文件cpu/arm920t/start.S中。下面分析cpu/arm920t/start.S的执行。

⑴设置异常向量

当一个异常或中断发生时,cpu会把pc指针设置为一个特定的存储器地址,这一地址存放在一个被称为向量表(vector table)的特定地址范围内。向量表的入口是一些跳转指令,跳转到专门处理某个特定异常或中断的子程序处。ARM的异常向量表及其各个异常向量的介绍如下表所示。

地址

异常类型

进入模式

说明

0x00000000

复位

管理模式

复位电平有效时产生

0x00000004

未定义指令

未定义指令模式

遇到ARM处理器无法识别的指令时产生

0x00000008

软件中断

管理模式

SWI指令产生

0x0000000c

预取指令

中止模式

当获取的指令不存在时产生

0x00000010

数据访问

中止模式

当获取的数据不存在时产生

0x00000014

保留

保留

保留

0x00000018

IRQ

IRQ模式

中断请求有效,并且CRSR中的I位为0

0x0000001c

FIQ

FIQ模式

快速中断请求有效,并且CRSR中的F位为0

cpu/arm920t/start.S开头有如下代码用于设置上述异常向量表。

 

       上述代码表明,当一个异常产生时,cpu根据异常号,在异常向量表中找到对应的异常向量,然后执行异常向量处的跳转指令,cpu跳转到对应的异常处理程序执行。例如,复位异常向量的指令“b start_code”决定u-boot启动后将自动跳转到标号“start_code”处执行。

⑵CPU进入管理模式(SVC)

在cpu/arm920t/start.S文件中有如下代码:

 

通过上述代码,u-boot将cpu的工作模式设置为管理模式,并将普通中断IRQ和快速中断FIQ的禁止位置1,从而屏蔽IRQ和FIQ中断。

⑶设置控制寄存器地址

在cpu/arm920t/start.S文件中有如下代码:

 

对于smdk2410开发板,上述代码完成WATCHDOG、INTMSK、INTSUBMSK、CLKDIVN这4个寄存器地址的设置。相关寄存器地址需要查看smdk2410的datasheet。

⑷关闭看门狗

在cpu/arm920t/start.S文件中有如下代码:

 

       上述代码向看门狗控制寄存器写入0,从而关闭看门狗。否则,在u-boot启动过程中,cpu可能因为看门狗定时器超时而不断重启。

⑸屏蔽中断

在cpu/arm920t/start.S文件中有如下代码:

 

       上述代码向主中断屏蔽寄存器INTMSK写入0xffffffff,即将INTMSK寄存器全部置1,从而屏蔽对应的中断。INTMSK的每一位对应SRCPND(源中断未决寄存器)中的一位,表明SRCPND相应位代表的中断请求是否会被cpu处理。

在cpu/arm920t/start.S文件中有如下代码:

 

上述代码屏蔽了所有SUBSRCPND中对应的中断。因为INTSUBMSK中的每一位对应SUBSRCPND中的一位,表明SUBSRCPND相应位代表的中断请求是否会被cpu所处理。

具体的中断和相关寄存器位需要查阅芯片的datasheet

⑹设置MPLLCON、UPLLCON和CLKDIVN

cpu上电几毫秒后,晶振输出稳定,主频FCLK=Fin(晶振频率),cpu开始执行指令。但实际上,FCLK可以高于Fin。为了提高系统时钟,需要用软件来启动PLL(锁相环)。这就需要设置MPLLCON、UPLLCON和CLKDIVN这3个寄存器。

CLKDIVN寄存器用于设置FCLK、HCLK、PCLK三者之间的比例,可以根据下表设置(其实应该根据smdk2410的datasheet来设置)。

CLKDIVN

说明

初始化

HCLK

[2:1]

00:HCLK=FCLK/1       01:HCLK=FCLK/2

00

PCLK

[0]

0: PCLK=HCLK/1        1:PCLK=HCLK/2

0

在cpu/arm920t/start.S文件中有如下代码:

 

       上述代码将CLKDIVN寄存器设置为3,也就是将HCLK和PCLK分别设置为01和1,因此HCLK=FCLK/2,PCLK=HCLK/2。由此得出FCLK、HCLK、PCLK三者之间的比例关系为4:2:1。

       Smdk2410中的MPLLCON寄存器用于设置FCLK与Fin的倍数,MPLLCON的位[19:12]称为MDIV,位[9:4]称为PDIV,位[1:0]称为SDIV。smdk2410的FCLK与Fin满足下面的公式:

Mpll=(m*Fin)/(p*2s),其中,m=(MDIV+8),p=(PDIV+2),s=SDIV。

MPLLCON与UPLLCON的寄存器的值要参考smdk2410的datasheet。

输入频率

输出频率

MDIV

PDIV

SDIV

12.0000MHz

48.00MHz

120(0x78)

2

3

12.0000MHz

202.80MHz

161(0xa1)

3

1

例如,将smdk2410时钟频率设置为48MHz时,系统可以稳定运行,因此设置MPLLCON与UPLLCON为:

MPLLCON=(0xA1<<12)|(0x03<<4)|(0x01)

UPLLCON=(0x78<<12)|(0x02<<4)|(0x03)

提示:MPLLCONUPLLCON的初始化代码实际上位于board/samsung/smdk2410/smdk2410.c中,该文件存放u-boot第二阶段的初始化代码。

⑺关闭内部指令集(MMU)和cache

在cpu/arm920t/start.S文件中有如下代码:

 

cpu_init_crit代码段在u-boot正常启动时才需要执行,若将u-boot从内存中启动,则应该注释掉这段代码。

下面分析cpu_init_crit代码段到底做了什么。在cpu/arm920t/start.S文件中有如下代码:

 

       在上述代码中,c0,c1,c7,c8都是ARM920t的协处理器CP15的寄存器。其中c7是cache控制寄存器,c8是TLB控制寄存器。第240~242行代码将0写入c7,c8,使cache和TLB内容无效。第247~252行代码关闭MMU,这是通过修改协处理器CP15的c1寄存器来实现的,CP15的c1寄存器格式如下表(可参考arm920t的数据手册)。可见,第247~252行代码是通过将c1寄存器的M位置0来关闭MMU的。

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

.

.

V

I

.

.

R

S

B

.

.

.

.

C

A

M

※V表示异常向量表所在的位置,0表示异常向量在0x00000000;1表示异常向量在0xFFFF0000;

※I为0时表示关闭Icaches;为1时表示开启Icaches;

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

※B为0表示CPU为小字节序;为1表示CPU为大字节序;

※C为0表示关闭Dcaches;为1表示开启Dcaches;

※A为0表示数据访问时不进行地质对齐检查;为1表示数据访问时进行地址对齐检查;

※M为0表示关闭MMU;为1表示开启MMU。

⑻初始化存储控制器

       内存初始化依赖于开发板,因此其初始化代码一般在board目录下对应的目录中,针对smdk2410,其内存初始化是在board/samsung/smdk2410/lowlevel_init.S中完成的。在board/samsung/smdk2410/lowlevel_init.S中有如下一段代码:

 

       在上述代码中,lowlevel_init设置了13个寄存器来对存储控制器时序进行初始化。当u-boot从NOR Flash启动时,由于u-boot连接时确定的地址是内存中的地址,而此时u-boot还在NOR Flash中,因此最终需要将之从NOR Flash中复制到RAM中运行。

       由于NOR Flash的起始地址为0,而u-boot加载到内存的起始地址是TEXT_BASE,所以SMRDATA标号在Flash中的地址就是SMRDATA减去TEXT_BASE。

       因此,lowlevel_init的作用是将SMRDATA开始的13个值复制给开始地址[BWSCON]的13个寄存器,从而完成存储控制器的设置。

⑼复制u-boot第二阶段代码到RAM中

在cpu/arm920t/start.S文件中有如下代码:

 

       在上述代码中,

第179行:当CONFIG_SKIP_RELOCATE_UBOOT宏被定义时,将跳过上述代码;

第181行:adr是相对寻址指令,adr r0,_start表示将利用PC寄存器的当前值减去该处代码连接地址与_start标号地址的偏移得到的相对地址传递给r0,也就是r0表示u-boot的运行地址;

第182行:将u-boot连接地址TEXT_BASE赋值给r1;

第183行:比较u-boot当前运行地址与连接地址是否一致;

第184行:如果一致,则无需赋值代码,跳至stack_setup标号处;

第186行:将_armboot_start表示的u-boot代码段地址赋值给r2;

第187行:将_bss_start表示的BSS段起始地址赋值给r3;

第188行:从连接器脚本可以看出,BSS段之前是代码段和数据段,因此r3-r2得到是代码段和数据段的总长度。这里将其赋值给r2;

第189行:再将r2+r0赋值给r2,这时r2表示需要复制的u-boot的结束地址;

第192~193行:利用多寄存器寻址提高复制效率,r0和r1自动生长;

第194~195行:判断r0是否等于r2,即是否到达结束地址,若r0

⑽设置栈

       栈是执行C程序的必要条件,因此,在进入C语言实现的初始化代码之前,需要通过汇编进行栈的初始化。在cpu/arm920t/start.S文件中有如下代码:

 

 

       根据上述代码,ARM处理器栈指针sp默认向下生长,因此只需要将sp指针指向一段低地址没有被使用的内存,即可完成栈的设置。

⑾清楚BSS段

       在调用C代码之前,还需要由u-boot将BSS段中存放的未初始化全局变量、静态变量清零。在cpu/arm920t/start.S文件中有如下代码:

 

第209~211行:执行完成后,r0、r1将分别存放BSS段的起始地址和结束地址,r2被清零;

第213~216行:执行循环,将r0、r1之间的区域清零。

⑿跳转到第二阶段代码入口

       经过上述初始化过程后,u-boot已经具备了在内存中执行C语言的能力,现在只需执行一条ldr指令就可以跳转至内存中的第二阶段初始化入口start_armboot,在cpu/arm920t/start.S文件中有如下代码:

 

 

 

 

 

 

2、u-boot启动流程的第二阶段代码分析

       u-boot启动流程的第二阶段初始化代码的入口start_armboot在lib_arm/board.c中定义,u-boot启动的第二阶段流程如下图:

 

       首先,介绍一些重要的数据结构

  • gd_t结构体

u-boot使用gd_t结构体来存储全局数据区的数据,这个结构体在include/asm-arm/global_data.h中定义:

 

u-boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址,arm/global_data.h中有如下代码:

 

DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。该声明避免了编译器把r8分配给其他变量。对于任何想要访问全局数据区的代码,只要在其开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,就可以使用gd指针访问全局数据区了。

       根据u-boot内存分配图可以计算gd的值,

gd=TEXT_BASE-CONFIG_SYS_MALLOC_LEN-sizeof(gd_t)

这与lib_arm/board.c文件中start_armboot()函数中对gd指针的初始化一致,lib_arm/board.c文件中的相关代码如下:

 

       上述代码中,全局标号_armboot_start在cpu/arm920t/start.S文件中有定义,

 

在这里,_armboot_start在此处取_start标号的值,也就是u-boot镜像的起始地址TEXT_BASE。

  • bd_t结构体

bd_t结构体用于存放板级相关的全局数据,是gd_t中结构体指针成员bd的结构体类型,其在include/asm-arm/u-boot.h文件中定义如下:

 

       u-boot启动内核时要给内核传递参数,这时需要使用gd_t、bd_t结构体中的信息来设置标记列表。

  • init_sequence数组

u-boot使用一个数组init_sequence来存储大多数开发板都要执行的初始化函数的函数指针。init_sequence数组中有较多的编译选项。init_sequence数组在lib_arm/board.c文件中定义,相关代码如下:

 

       在上述代码中,board_init函数在board/samsung/smdk2410/smdk2410.c中定义,该函数配置了MPLLCON、UPLLCON和部分GPIO寄存器的值,还设置了u-boot机器码和内核启动参数地址。具体代码如下:

 

dram_init函数在board/samsung/smdk2410/smdk2410.c中定义,具体代码如下:

 

       由于smdk2410使用两片32MB的SDRAM组成了64MB的内存,这两片内存连接在存储控制器的BANK6上,地址空间是0x30000000~0x34000000。在include/configs/smdk2410.h中,PHYS_SDRAM_1和PHYS_SDRAM_1_SIZE分别被定义为0x30000000和0x04000000。

  • 对start_armboot()函数的分析

start_armboot()函数在lib_arm/board.c中定义,具体代码及注释如下:

 

 

 

 

 

 

       从上述代码的注释中可知,start_armboot()在进行一系列的初始化(包括对全局数据指针gd、堆、各种设备和环境变量的初始化)后,最终调用main_loop()进入u-boot主循环中。

  • main_loop()函数的分析

main_loop()函数在common/main.c文件中定义,做的都是与具体平台无关的工作,主要包括初始化启动次数限制机制、设置软件版本号、打印启动信息、解析命令等。

    • 设置启动次数有关参数

u-boot进入main_loop()函数后,首先根据配置加载已经保留的启动次数,并且根据配置判断是否超过启动次数。common/main.c文件中的相关代码如下:

 

 

第313~315行:加载保存的启动次数至变量bootcount,并将启动次数加1后重新保存;

第316~317行:打印启动次数,然后设置启动次数到环境变量“bootcount”;

第318~319行:从环境变量“bootlimit”中读出启动变量限制至bootlimit;

第312~320行:实现启动次数限制功能。启动次数限制可以设置为一个启动次数,然后保存在Flash存储器的特定位置,当到达启动次数后,u-boot无法启动。该功能适用于商业产品中。

    • 启用Modem功能

common/main.c文件中的第322~331行实现Modem功能。如果系统中有Modem,打开该功能可以接受其他用户通过电话网络的拨号请求。Modem功能通常供一些远程控制的系统使用。

 

    • 设置版本号、初始化命令自动完成功能

common/main.c文件中如下代码用来设置版本号、初始化命令自动完成功能。

 

 

第333~339行:支持动态版本号,version_string变量是在其他文件中定义的一个字符串变量,当用户改变u-boot版本时会更新该变量。在打开动态版本支持功能后,u-boot在启动时会显示最新版本号。

第350行:设置命令行自动完成功能,该功能与Linux的Shell类似,当用户输入一部分命令后,可以按Tab键补全命令的剩余部分;

第353~370行:判断是否支持预启动命令,若果支持,则从环境变量中取出相关命令执行。

注意:在嵌入式系统中,不同系统的Flash存储空间不同。对于一些Flash空间比较紧张的设备来说,通过宏开关关闭一些不必要的功能,如命令自动完成,可以减小u-boot编译后的文件大小。u-boot正是基于这种思想才在代码中添加了大量的宏开关,以便于工程师根据自己的需要进行裁剪。

    • 启动延时和启动菜单

在u-boot进入主循环之前,如果配置了启动延时功能,需要等待用户从串口或网络接口输入。如果用户按下任意键打断启动流程,则向终端打印出一个启动菜单。common/main.c文件中有如下代码:

 

 

 

第372~376行:获得表示启动延时的环境变量“bootdelay”,如果不存在,则从配置宏CONFIG_BOOTDELAY获取,并保持在变量bootdelay中。

第388~395行:判断启动次数是否超过限制,如果是,则执行环境变量“altbootcmd”表示替代启动命令,替代启动命令可以用来为未授权的商业产品保留一些功能;

第396~415行:从环境变量“bootcmd”获取启动命令,在变量bootdelay表示的启动延时大于等于0且启动流程未被中止(abortboot()返回1)的情况下,执行启动命令。

第417~429行:检查是否是因为CONFIG_MENUKEY宏指定的按键被按下而中止了启动,如果是,则从环境变量“menucmd”中取出菜单命令并执行,以调出启动菜单。

第432~437行:如果配置了视频设备,则显示启动图标。

    • 执行命令循环

common/main.c文件中有如下代码:

 

 

U-boot的各个功能设置完毕后,main_loop函数在第477行进入一个for死循环,该循环不断使用readline()函数(第456行)从控制台(一般是串口)读取用户的输入,再通过run_command()函数(第480行)执行命令。

第461~462行:如果用户直接按回车键(此时命令长度len等于0),就会在第480行重复执行上一次的命令;

第477~478行:如果用户按Ctrl+C组合键(此时命令长度等于-1),终端将输出“”,表示上次命令执行被中断了。

 

 

你可能感兴趣的:(Linux操作系统笔记)