u-boot

来源:互联网 发布:理化实验室软件 编辑:程序博客网 时间:2024/05/29 04:54

1 简介

U-Boot,全称 Universal Boot Loader,是遵循GPL条款的开放源码项目。U-Boot的作用是系统引导。
u-boot是一种普遍用于嵌入式系统中的Bootloader,Bootloader是在操作系统运行之前执行的一小段程序,通过它,我们可以初始化硬件设备、建立内存空间的映射表,从而建立适当的软硬件环境,为最终调用操作系统内核做好准备。Boot Loader的主要运行任务就是将内核映象从硬盘上读到RAM中,然后跳转到内核的入口点去运行,即开始启动操作系统。系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的Boot Loader程序。

1.1 嵌入式Linux软件结构与分布

在一般情况下嵌入式Linux系统中的软件主要分为以下及部分:
  1. 引导加载程序:其中包括内部ROM中的固化启动代码和Boot Loader两部分。
    而这个内部固化ROM是厂家在芯片生产时候固化的,作用基本上是引导Boot Loader。有的芯片比较复杂,比如Omap3,他在flash中没有代码的时候有许多启动方式:USB、UART或以太网等等。而S3C24x0则很简单,只有Norboot和Nandboot。
  2. Linux kernel 和drivers。
  3. 文件系统。包括根文件系统和建立于Flash内存设备之上的文件系统(EXT4、UBI、CRAMFS等等)。它是提供管理系统的各种配置文件以及系统执行用户应用程序的良好运行环境的载体。
  4. 应用程序。用户自定义的应用程序,存放于文件系统之中。

这里写图片描述

1.2 在嵌入式Linux中为什么要有BootLoader

在linux内核的启动运行除了内核映像必须在主存的适当位置,CPU还必须具备一定的条件:
  1. CPU 寄存器的设置
    R0=0;
    R1=Machine ID(即Machine Type Number,定义在linux/arch/arm/tools/mach-types);
    R2=内核启动参数在 RAM 中起始基地址;
  2. CPU 模式
    必须禁止中断(IRQs和FIQs);
    CPU 必须 SVC 模式;
  3. Cache 和 MMU 的设置
    MMU 必须关闭;
    指令 Cache 可以打开也可以关闭;
    数据 Cache 必须关闭;

但是在CPU刚上电启动的时候,一般连内存控制器都没有配置过,根本无法在内存中运行程序,更不可能处在Linux内核的启动环境中。为了初始化CPU及其他外设,使得Linux内核可以在系统主存中跑起来,并让系统符合Linux内核启动的必备条件,必须要有一个先于内核运行的程序,他就是所谓的引导加载程序(Boot Loader)。
而Boot Loader并不是Linux才需要,是几乎所有的运行操作系统的设备都具备的。我们的PC的BOIS就是Boot Loader的一部分(只是前期引导,后面一般还有外存中的各种Boot Loader),对于Linux PC来说,Boot Loader = BIOS + GRUB/LILO。

2 目录结构

  • board中存放于开发板相关的配置文件,每一个开发板都以子文件夹的形式出现。
  • Commom文件夹实现u-boot行下支持的命令,每一个命令对应一个文件。
  • cpu中存放特定cpu架构相关的目录,每一款cpu架构都对应了一个子目录。
  • Doc是文档目录,有u-boot非常完善的文档。
  • Drivers中是u-boot支持的各种设备的驱动程序。
  • Fs是支持的文件系统,其中最常用的是JFFS2文件系统。
  • Include文件夹是u-boot使用的头文件,还有各种硬件平台支持的汇编文件,系统配置文件和文件系统支持的文件。
  • Net是与网络协议相关的代码,bootp协议、TFTP协议、NFS文件系统得实现。
  • Tools是生成U-boot的工具。

其中比较重要的目录就是/board、/cpu、/drivers和 /include目录,如果想实现u-boot在一个平台上的移植,就要对这些目录进行深入的分析。

3 启动方式

3.1 网络启动方式

图3.1 Bootloader网络启动方式示意图
如图3.1所示,里面主机和目标板,他们中间通过网络来连接,首先目标板的DHCP/BIOS通过BOOTP服务来为Bootloader分配IP地址,配置网络参数,这样才能支持网络传输功能。我们使用的u-boot可以直接设置网络参数,因此这里就不用使用DHCP的方式动态分配IP了。接下来目标板的Bootloader通过TFTP服务将内核映像下载到目标板上,然后通过网络文件系统来建立主机与目标板之间的文件通信过程,之后的系统更新通常也是使用Boot Loader的这种工作模式。工作于这种模式下的Boot Loader通常都会向它的终端用户提供一个简单的命令行接口。

3.2 磁盘启动方式

这种方式主要是用在台式机和服务器上的,这些计算机都使用BIOS引导,并且使用磁盘作为存储介质,这里面两个重要的用来启动linux的有LILO和GRUB,这里就不再具体说明了。

3.3 Flash启动方式

这是我们最常用的方式。Flash有NOR Flash和NAND Flash两种。NOR Flash可以支持随机访问,所以代码可以直接在Flash上执行,Bootloader一般是存储在Flash芯片上的。另外Flash上还存储着参数、内核映像和文件系统。这种启动方式与网络启动方式之间的不同之处就在于,在网络启动方式中,内核映像和文件系统首先是放在主机上的,然后经过网络传输下载进目标板的,而这种启动方式中内核映像和文件系统则直接是放在Flash中的,这两点在我们u-boot的使用过程中都用到了。

4 启动流程

系统启动的入口点。既然我们现在要分析u-boot的启动过程,就必须先找到u-boot最先实现的是哪些代码,最先完成的是哪些任务。另一方面一个可执行的image必须有一个入口点,并且只能有一个全局入口点,所以要通知编译器这个入口在哪里。由此我们可以找到程序的入口点是在/board/lpc2210/u-boot.lds中指定的,其中ENTRY(_start)说明程序从_start开始运行,而他指向的是cpu /arm7tdmi/start.o文件。因为我们用的是ARM7TDMI的cpu架构,在复位后从地址0x00000000取它的第一条指令,所以我们将Flash映射到这个地址上,这样在系统加电后,cpu将首先执行u-boot程序。
大多数BootLoader都分为stage1和stage2两大部分,U-boot也不例外。依赖于cpu体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。

4.1 stage1(start.s代码结构)

  1. CPU 自身初始化:包括MMU,Cache,时钟系统,SDRAM 控制器等的初始化
  2. 重定位:把自己从非易失性存储器搬移到 RAM 中
  3. 分配堆栈空间,设置堆栈指针
  4. 清零 BSS 数据段
  5. 跳转到第二阶段入口函数 start_armboot()

4.2 stage2(C语言代码部分)

  1. 为 U-boot 内部私有数据分配存储空间,并清零
  2. 依次调用函数指针数组 init_sequence 中定义的函数进行一系列的初始化
  3. 如果系统支持 NOR Flash,调用flash_init ()和display_flash_config ()初始化并显示检测到的器件信息
  4. 如果系统支持 LCD 或VFD,调用lcd_setmem()或vfd_setmem()计算帧缓(Framebuffer)大小,然后在BSS 数据段之后为Framebuffer 分配空间,初始化gd->fb_base 为Framebuffer 的起始地址
  5. 调用 mem_malloc_init()进行存储分配系统(类似于C 语言中的堆)的初始化和空间分配
  6. 如果系统支持 NAND Flash,调用nand_init ()进行初始化
  7. 如果系统支持 DataFlash,调用AT91F_DataflashInit()和dataflash_print_info()进行初始化并显示检测到的器件信息
  8. 调用 env_relocate ()进行环境变量的重定位,即从Flash 中搬移到RAM 中
  9. 如果系统支持 VFD,调用drv_vfd_init()进行VFD 设备初始化
  10. 从环境变量中读取 IP 地址和MAC 地址,初始化gd->bd-> bi_ip_addr 和gd->bd->bi_enetaddr
  11. 调用 jumptable_init ()进行跳转表初始化,跳转表在global_data 中,具体用途尚不清楚
  12. 调用 console_init_r()进行控制台初始化
  13. 如果需要,调用 misc_init_r ()进行杂项初始化
  14. 调用 enable_interrupts ()打开中断
  15. 如果需要,调用board_late_init()进行单板后期初始化,对于AT91SAM9260EK,主要是以太网初始化
  16. 进入主循环:根据用户的选择启动 linux,或者进入命令循环执行用户输入的命令
    这部分是一些相对变化不大的部分,我们针对不同的板子改变它调用的一些初始化函数,并且通过设置一些宏定义来改变初始化的流程,所以这些代码在移植的过程中并不需要修改,也是错误相对较少出现的文件。在文件的开始先是定义了一个函数指针数组,通过这个数组,程序通过一个循环来按顺序进行常规的初始化,并在其后通过一些宏定义来初始化一些特定的设备。在最后程序进入一个循环,main_loop。这个循环接收用户输入的命令,以设置参数或者进行启动引导。

5 命令

5.1 获取帮助help

查看当前U-boot版本中支持的所有命令。

# help

如果你想获取某条命令的更详细的帮助,可以使用:

# help [CommandName]

5.2 环境变量env

env(environment variables,简称ENV)与相关指令,和shell类似,U-Boot也有环境变量。
这里写图片描述
一些U-boot默认的环境变量如下:
这里写图片描述

5.2.1 设置环境变量

# env set <name> [arg ...]
5.2.1.1 serverip
# env set serverip 192.168.1.218
5.2.1.2 ipaddr
# env set ipaddr 192.168.1.116
5.2.1.3 gatewayip
# env set gatewayip 192.168.1.1
5.2.1.4 netmask
# env set netmask 255.255.255.0
5.2.1.5 ethaddr
# env set ethaddr 00:10:20:30:40:50

5.2.1.6 baudrate

# env set baudrate 115200
5.2.1.7 bootdelay
# env set bootdelay 10
5.2.1.8 stdin
# env set stdin serial
5.2.1.9 stdout
# env set stdout serial
5.2.1.10 stderr
# env set stderr serial
5.2.1.11 bootcmd
# 内核镜像自动下载# env set bootcmd tftp 0x40800000 zImage# env set bootcmd bootr
5.2.1.12 bootargs
# 设置内核启动参数(nfs挂载):# env set bootargs root=/dev/nfs rw nfsroot=主机IP:/nfsroot ip=开发板IP console=ttySAC0,115200# env set bootargs root=/dev/nfs rw nfsroot=192.168.1.218:/opt/filesystem ip=192.168.1.150:192.168.1.218:192.168.1.1:255.255.255.0 console=ttySAC0,115200# 设置内核启动参数(加载):# env set bootargs root=/dev/mtdblock3 rootfstype=yaffs2 init=/init console=ttySAC0,115200

5.2.2 删除环境变量

# env set <name>

5.2.3 恢复默认环境变量

# env default -f

5.2.4 保存环境变量

# env save

5.3 go

这里写图片描述
第1个参数是要执行程序的入口地址。
第2个可选参数是传递给程序的参数,可以不用。

# go 0x1500000

5.4 tftp

这里写图片描述

# 从主机下载内核到内存# tftp 0x40800000 zImage

5.5 ping

这里写图片描述

# ping 192.168.1.1

5.6 bootm

这里写图片描述
1. 读取头部,把内核拷贝到合适的地方
2. 把参数给内核准备好,并告诉内核参数的首地址
3. 引导内核

# bootm 0x40800000

5.7 nand flash

nand info
nand device
显示flash的信息

# nand read addr off size# nand read 0x10800000 600 10

表示从nand上1536×512个字节开始处(1536是600的十进制),读取16×512个字节(16是10的10进制)到内存0x10800000 处

# nand write addr off size# nand write 0x108000000 0 100

表示从内存0x108000000的位置上读取256*512的数据(256是100的十进制)到nand上0的位置处

# nand erase [clean] [off size]

在擦之前会先check是不是坏块,如果是坏块,就不能做擦的动作了。

# nand erase 1cc000 100

5.8 mmc

这里写图片描述

 # mmc read 0x10800000 600 10

表示从mmc上1536×512个字节开始处(1536是600的十进制),读取16×512个字节(16是10的10进制)到内存0x10800000 处

# mmc write 0x108000000 0 100

表示从内存0x108000000的位置上读取256*512的数据(256是100的十进制)到mmc上0的位置处

原创粉丝点击