U-BOOT全线移植分析系列之二――U-boot基础

【摘要】本节介绍了U-boot 的基本概念。首先介绍了U-boot 源代码的目录结构,并给出了一 个实例。接着简单介绍了U-boot 支持的基本功能、常见命令和环境变量。最后详细分析了U-boot 启动的两个阶段,重点介绍了加载 拷贝代码至RAM 中 的过程。

 

【关键词】bootloaderU-boot ,环境变量, stage1 ,位置无关,代码搬移

 

U-boot 基础

现在为Linux 开放源代码Bootloader 有很多, blob redboot U-BOOT 等,其中U-BOOT 是目前用来开 发嵌入式系统引导代码使用最为广泛的Bootloader 。它支持 POWERPC ARM MIPS X86 等处理器,支持嵌入式操作系统有LinuxVxworksNetBSD 等。

 

2.1 U-boot 源代码目录结构

|-- board  平台依赖,存放电路板相关的目录文件

|-- common  通用多功能函数的实现

|-- cpu  平台依赖,存放cpu 相关的目录文件

|-- disk  通用。硬盘接口程序

|-- doc  文档

|-- drivers  通用的设备驱动程序,如以太网接口驱动

|-- dtt

|-- examples  应用例子

|-- fs  通用存放文件系统的程序

|-- include  头文件和开发板配置文件,所有开发板配置文件放在其configs

|-- lib_arm  平台依赖,存放arm 架构通用文件

|-- lib_generic  通用的库函数

|-- lib_i386  平台依赖,存放x86 架构通用文件

|-- lib_m68k  平台依赖

|-- lib_microblaze  平台依赖

|-- lib_mips  平台依赖

|-- lib_nios   平台依赖

|-- lib_ppc 平台依赖,存放ppc 架构通用文件

|-- net  存放网络的程序

|-- post  存放上电自检程序

|-- rtc  rtc 的驱动程序

`-- tools  工具

 

详细实例:

²       board 开发板相关的源码 ,不同的板子对应一个子目录,内 部放着主板相关代码。 Board/at91rm9200dk/at91rm9200.c, config.mk, Makefile, flash.c ,u-boot.lds 都和具体开发板的硬件和地址分配有关。

 

²       common :与体系结构无关的代码文件,实现了 u-boot 所有命令,其中内置了一个 shell 脚本解释器 (hush.c, a prototype Bourne shell grammar parser), busybox 中也使用了它。

 

²       cpu : 与 cpu 相关代码文件,其中的所有子目录都是以 u-boot 支持的 cpu 命名

cpu/at91rm9200/at45.c, at91rm9200_ether.c, cpu.c, interrupts.c serial.c, start.S, config.mk, Makefile 等。其中:

cpu.c 负责初始化CPU 、设置指令Cache 和数据Cache 等;

interrupt.c 负责 设置系统的各种中断和异常 ,比如快速中断、开关中 断、时钟中断、软件中断、预取中止和未定义指令等;

start.S 负责 u-boot 启动时执行的第一个文件, 它主要是设置系统堆栈和工作方式,为跳转到C 程序入口点做准备;

at91rm9200_ether.c serial.c 很 重要,这是系统能够下载资源的前提。

 

²       disk :设备分区处理代码。

²       doc u-boot 相关文档。

 

²       driversu-boot 所支持的设备驱动代码, 网卡 、支持CFIFlash 、串口和USB 总线等。

²       fs: u-boot 所支持文件系统访问存取 代码, jffs2

 

²       include u-boot head 文件,主要是与各种硬件平台相关的头文件,如 include/asm-arm/arch-at91rm9200/AT91RM9200.h( 硬件寄存器名称及地址的定义 ), hardware.h 内存及 flash 地址以及 IO 物理地址和虚拟地址的定义 ),include/asm-arm/proc-armv (与具体的CPU 无关,无需移植)。

 

²       net : 与网络有关的代码, BOOTP 协议、 TFTP 协议、 RARP 协议代码实现 . 无需移植。

 

²       lib_arm :与 arm 体系相关的代码。

²       tools :编译后会生成 mkimage 工具,用来对生成的 raw bin 文件加入 u-boot 特定的 image_header.

 

2.2 U-Boot 支持的主要功能

主要功能如下:

²       系统引导, 支持 NFS 挂载 RAMDISK (压缩或非压缩)形式的根文件 系统;

²       支持 NFS 挂载、从 FLASH 中引导压缩或非压缩系统内核;

²       基本辅助功能,强大的 操作系统接口功能; 可灵活设置、传递多个关键参数给操作系统 ,适合系统在不同开发阶段的调试要求与产品发布,尤对 Linux 支持最为强劲;

²       支持目标板环境参数多种存储方式,如 FLASH NVRAM EEPROM

²       CRC32 校验,可校验 FLASH 中内核、 RAMDISK 镜像文件是否完好;

²       设备驱动,串口、 SDRAM FLASH 、以太网、 LCD NVRAM EEPROM 、键盘、 USB PCMCIA PCI RTC 等驱动支持;

²       上电自检功能 SDRAM FLASH 大小自动检测; SDRAM 故障检测; CPU 型 号;

²       特殊功能, XIP 内核引导。

 

2.3 U-boot 命 令介绍及环境变量

²       ?得到所有命令列表

²       Help help usb, 列出 USB 功能的使用说明

²       ping :注意只能开发板 PING 别的机器( AT91RM9200 不支持,需要进行配置)

²       setenv: 设置环境变量

Ø        setenv serverip 192.168.0.1

Ø        setenv ipaddr 192.168.0.56

Ø        setenv bootcmd ‘tftp 32000000 vmlinux; kgo 32000000’

²       saveenv :保存环境变量。在设置好环境变量以后, 保存变量值

²       tftp tftp 32000000 vmlinux, server IP= 环境变量中设置的serverip )中 /tftpboot/ 下的 vmlinux 通过 TFTP 读入到物理内存 32000000

²       bootp- 通过网络用 BootP/TFTP 协 议来启动映象

²       tftpboot- 通过网络用 TFTP 协议、设置服务器和客户机的 IP 地址进行映象文件传送

²       kgo: 起动没有压缩的 linux 内核, kgo 32000000 AT91RM9200 不支持)

²       bootm :起动 UBOOT  TOOLS 制作的压缩LINUX 内核 , bootm 3200000

²       protect FLASH 进行写保护或取消写保护,protect on 1:0-3( 就是对第一块FLASH0-3 扇区进行保护)protect off 1:0-3 取消写保护

²       erase 删除FLASH 的扇区, erase 1:0-2( 就是对每一块FLASH0-2 扇区进行删除)

²       cp 在内存中复制内容, cp 32000000 0 40000( 把内存中0x32000000 开始的0x40000 字节复制到0x0)

²       mw : 对 RAM 中 的内容写操作, mw 32000000 ff 10000( 把内存 0x32000000 开始的 0x10000 字节设为 0xFF)

²       md 修改 RAM 中的内容 , md 32000000 (内存的起始地址)

²       flinfo 列出 flash 的信息

²       loadb 准备用 KERMIT 协议接收来自 kermit 或超级终端传送的文件。

²       nfs nfs 32000000 192.168.0.12:aa.txt ,把 192.168.0.12(LINUX NFS 文件系统 ) 中的 NFS 文件系统中的 aa.txt 读入内存 0x32000000 处。

 

最常用的几个命令如下:

²       go- 在地址 'addr' 处开始程序执行

²       run- 运行一个环境变量所定义的命令

²       bootm- 内存中 进行运行经过 mkimage 加工的程序映象

²       loadb- 通过串口线 (kermit mode) 来装载二进制 文件

²       printenv- 打印环境变量

²       setenv- 设置环境变量

²       saveenv 保存环境变量到内存

²       tftp -通过网络下载文件

²       protect erase flash 读写

 

下面是 U-BOOT 中的简单环境变量

²       baudrate 波特率

²       bootdelay         boot 延迟

²       bootcmd   Boot 命令

²       bootargs   Boot 参数,传递给内核

²       bootfile 默认下载启动的内核映象

²       ipaddr       客户机 IP 地址

²       serverip    服务器地址

²       loadaddr   装载地址

²       ethaddr     网卡 MAC 地址

 

2.4 U-Boot 的 启动流程分析

和大多数的Bootloader 一 样,U-BOOT 的启动分为两个阶段两个部分,依赖于CPU 体系结构的代码主要放在stage1 ,且用汇编 来实现,而stage2 则通常用C 语言来实现,这样可以实现复杂的功能,而且具有更好的可读性和可移植性。

 

下面分别分析一下这两个阶段的启动流程:

第一阶段:基本的硬件初始化,为第二阶段程序运行建立 环境 (cpu/ at91rm9200 /start.s 文件的代码部分):

××××××××××××××××××××××××××××

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")

OUTPUT_ARCH(arm)

ENTRY(_start) // 程序的入口在 /cpu/××××/start.s 中定义

SECTIONS

{

        . = 0x00000000; // 程序链接的地址

 

        . = ALIGN(4);

        .text      :

        {

          cpu/at91rm9200/start.o  (.text)

          *(.text)

        }

 

        . = ALIGN(4);

        .rodata : { *(.rodata) }

 

        . = ALIGN(4);

        .data : { *(.data) }

 

        . = ALIGN(4);

        .got : { *(.got) }

 

  __u_boot_cmd_start = .;

  .u_boot_cmd : { * (.u_boot_cmd) }

  __u_boot_cmd_end = .;

 

        armboot_end_data = .; // 代码段结束地址

 

        . = ALIGN(4);

        .bss : { *(.bss) }

 

        armboot_end = .;   // 整个 U-boot 印象的结束地址

}

××××××××××××××××××××××××××××

 

在此需要定义程序入口, 由于一个可执行的 Image 必须要有 一个入口点 ,并且只能有一 个 全局入 口,通常这个 入口就在 ROM (flash)0x0 地址,因此,必须通知编译器以使其知道这个入口, 该 工作可通过修改连接器脚本 u-boot.lds 来完成 ,该阶段需要依次完成的工作一般包括:

²       CPU 自身的初始化,它包括: CPU 运 行模式的设置(管理模式)、设置异常的入口地址和异常处理函数、运行时钟频率的设置等工作。

²       初始化 GPIO 和内存控制器。

²       为拷贝 Stage2 准备 RAM 空间。

²       进行自拷贝,将 U-BOOT Stage2 拷贝到 RAM 中。

²       设置好堆栈。

²       跳转到 Stage2 的入口,从而转到 RAM 中执行,该工作是调用指令 ldr pc, start armboot 来完成的。

××××××××××××××××××××××××××

1.1.2 开始, u-boot 有初始化 SDRAM 并拷贝自己到 SDRAM 运行的代码,而之前的版本就没有这个功能(详细查看下代码??的确如此,因此对于以前的版本 TEXT_BASE 没有起作用? 实际测试下??) board/ at91rm9200dk config.mk 文件(TEXT_BASE = 0x21f0000032M RAM 的设置,为第二阶 段程序在RAM 中的运行地址) 用于设置程序编译连接的起始地址,在程序中要特别注 意与 地址相关指令的使用

Board/at91rm9200dk/config.mk

TEXT_BASE = 0x21f00000u-boot 将被载入SDRAM 高端部分

注意,对于不同的系统 RAM 大小可能不一样,要根据实际情况调整

/config.mk

ifdef BOARD

sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk     # include board specific rules

endif

CPPFLAGS := $(DBGFLAGS) $(OPTFLAGS) $(RELFLAGS)              /

        -D__KERNEL__ -DTEXT_BASE=$(TEXT_BASE)     

LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)

export      TEXT_BASE PLATFORM_CPPFLAGS PLATFORM_RELFLAGS CPPFLAGS CFLAGS AFLAGS

 

对于 U-Boot 1.1.2 以前 的版本,并没有自拷贝的部分,若flash 中首地址存放的是非压缩的u-boot.bin 的,则启动部分是一直运行在flash 中的;但对于AT91RM9200 来 说,他通常有三个文件loader.bin, boot.bin, u-boot.binflash 中首先运行的是boot.bin 其将压缩的u-boot.bin.gz 解压拷贝到 TEXT_BASE 处 运行,此时已经在 RAM 中了,因此也无需实现自拷贝了。

 

对于 U-Boot 1.1.2 以后的版本,若像AT91RM9200boot.bin 这样的过渡程序,则将u-boot.bin.gz 解压到RAM 中,此时运行地址和链接地址相同,无 需拷贝;若没有,则u-boot.bin 将在flash 中执行初始化部分,然后将自身拷贝到RAM 中执行。

// 比较运行地址和链接地址,如果当前已经在 RAM 中运行了,则 无需拷贝到 RAM

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't reloc during debug         */

        beq     stack_setup

 

        ldr     r2, _armboot_start

        ldr     r3, _bss_start

        sub     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

 

当程序在Flash 中运行时,执行程序跳转时必须要使用 相对跳转指令 ,而不能使用绝对地址的跳转 ( 即直接对PC 操作) 。如果 使用绝对地址,那么,程序的取指是相对于当前 PC 位置向前或者向后的 32MB 空间内, 而不会跳入 SDRAM

×××××××××××××××××××××××××××××

 

在上述操作运行完成后,就进入到/lib_arm/board.c 中的start_armboot() 函数运行,并建立起了一个基本的环境,此时的物理内存空间的分布就变成了如图所示的情况。

转入boatloader stage2 的系统内存布局

 

第二阶段:运行U-BOOT 的主体部分

该阶段以程序跳转到lib_arm/board.c 中的 start_armboot 函数为标志,该函数同时也是C 语言的开始函数,是整 个启动代码的主体函数,同时还是整个U-BOOT 的主体函数,该函数主要完成以下工作:

²       调用一系列的初始化函数,初始化本阶段使用到的硬件,如:

×××××××××××××××××××

init_fnc_t *init_sequence[] = {

        cpu_init,           /* basic cpu dependent setup */

        board_init,                /* basic board dependent setup */

        interrupt_init,            /* set up exceptions */

        env_init,           /* initialize environment */

        init_baudrate,           /* initialze baudrate settings */

        serial_init,         /* serial communications setup */

        console_init_f,          /* stage 1 init of console */

        display_banner,         /* say that we are here */

        dram_init,         /* configure available RAM banks */

        display_dram_config,

#if defined(CONFIG_VCMA9)

        checkboard,

#endif

        NULL,

};

×××××××××××××××××××××

env_init :设置环境变量,初始化环境;

init_baudrate :设置串口的波特率;

serial_init :设置串口的工作方式;

dram_init :设置SDRAM 的起始地址和大小;

     

²       检查存储器分配和使用情况:获取 flash bank 分区情况、是否擦除、是否上锁等信息为以后 flash 相关命令使用;初始化系统内存分配函数,供后面的代码使用 malloc 等函数,U-BOOT 没有使用其他现成的库所有函数的实现均在文件中 ) ,如果系统有液晶等显示设备,一并在此分配显示内存。

²       打印内存,flash 、环境变量设置等信息。

²       等待几秒时间,如果有键盘输入,则进入命令模式,接收用户输入的命令 并解释执行(如启动操作系统,更新 flash 内容)。

²       如果在给定的时间内没 有用户输入或者在执行命令操作时收到了 用户要求启动内核的命令 (boot) 则将把操作系统内核和 根文件系统映像文件从 flash 中拷贝到 RAM 中 相应位置,并在设置内核启动参数后跳转到内核映像的首地址处执行。

×××××××××××××××××××××××××××××

U-BOOT 调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到 MEM_START 0x8000 地址处。在跳转时,要满足下列条件:

a) CPU 寄存器的设置:R00R1 =机器类型 ID ,本系统的机器类型ID193 R2 =启动参数标 记列表在 RAM 中的起始基地址;  

b) CPU 模式:必须禁止中断(IRQsFIQs)CPU 必须工作在SVC 模式;

c) CacheMMU 的设置:MMU 必须关闭;指令Cache 可以打开也可以关闭;数据Cache 必须关闭。

系统采用下列代码来进入内核函数:

void (*theKernel)(int zero, int arch);

theKernel = (void (*)(int, int))ntohl(hdr->ih_ep);

theKernel(0, bd->bi_arch_number); 其中,hdrimage_header_t 类型的结构体, hdr-> ih_ep entry point ,指向内核的第一条指令地址,即 Linux 操作系统下的 /kernel/arch/arm/boot/compressed/head.S 汇编程序 theKernel() 函数调用应该不会返回,如果该调用返回,则说明出错。

×××××××××××××××××××××××××××××

你可能感兴趣的:(linux,Flash,平台,makefile,linux内核,嵌入式操作系统)