2017-07-23 19:49:53 a6tk5qj8zh 阅读数 99
  • C语言嵌入式Linux编程第3期:程序的编译、链接和运行

    本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。

    1125 人正在学习 去看看 王利涛

  U-BOOT全线移植分析系列之四

――U-boot如何引导Linux内核启动?

 

Sailor_forever  sailing_9806@163.com 转载请注明

http://blog.csdn.net/sailor_8318/archive/2008/08/05/2773412.aspx

 

【摘要】本节介绍了U-boot使用Go或bootm启动linux内核的方法。首先介绍了mkimage的参数意义和bootm的详细执行流程。然后分析了如何利用mkimage生成内核映象的方法。对于bootm方式的内核是否压缩、-a、-e、运行地址等16种组合情况,给出了详细的测试过程,提出了6种可用方法种的三种最优解。

 

【关键字】:U-boot;AT91RM9200;bootm;mkimage;-a;-e;-c

 

四 U-boot如何引导Linux内核启动?
4.1  go命令引导未用mkimage生成的内核
4.1.1 非压缩内核Image

1)       运行地址!=链接地址0x20008000,不能启动

Uboot> tftp 21000000 Image;tftp 21100000 ramdisk;Go 21000000

。。。。

done

Bytes transferred = 6993691 (6ab71b hex)

## Starting application at 0x21000000 ...

Error: a 在哪提示的?

 

2)       运行地址=链接地址0x20008000,不能启动,难道是ramdisk的问题

Uboot> tftp 20008000 Image;tftp 21100000 ramdisk;go 20008000

。。。。

done

Bytes transferred = 6993691 (6ab71b hex)

## Starting application at 0x21000000 ...

Error: a

 

4.1.2 压缩内核zImage

1)       运行地址!=链接地址0x20008000,能启动,内核自解压成功,但是解压后的内核运行错误

Uboot> tftp 21000000 zImage;tftp 21100000 ramdisk;go 21000000

。。。。。。。。。。。

done

Bytes transferred = 6993691 (6ab71b hex)

## Starting application at 0x21000000 ...

Uncompressing linux............................................................. done, booting the kernel.

€?~??鄜屈

 

2)       运行地址==链接地址0x20008000,能启动,内核自解压成功,但是解压后的内核运行错误

 

Uboot> tftp 20008000 zImage;tftp 21100000 ramdisk; go 20008000

## Starting application at 0x20008000 ...

Uncompressing Linux............................................................. done, booting the kernel.

€?~??鄜屈

 

上面的ramdisk都是添加了uboot的头的,去掉头部再试试。去掉了还是不行,go方法的ramdisk的地址是怎么设置的??要详细看下uboot在ramdisk这块是如何跟内核交互的?

 

4.2 Mkimage参数意义解析
通过mkimage这个tool可以给zImage添加一个header:

typedef struct image_header {

        uint32_t    ih_magic; /* Image Header Magic Number      */

        uint32_t    ih_hcrc;    /* Image Header CRC Checksum    */

        uint32_t    ih_time;    /* Image Creation Timestamp   */

        uint32_t    ih_size;     /* Image Data Size           */

        uint32_t    ih_load;    /* Data     Load  Address              */

        uint32_t    ih_ep;              /* Entry Point Address             */

        uint32_t    ih_dcrc;    /* Image Data CRC Checksum        */

        uint8_t             ih_os;               /* Operating System         */

        uint8_t             ih_arch;    /* CPU architecture         */

        uint8_t             ih_type;    /* Image Type                  */

        uint8_t             ih_comp;  /* Compression Type                */

        uint8_t             ih_name[IH_NMLEN];    /* Image Name                */

} image_header_t;

 

此header是如何生成的?利用u-boot里面的mkimage工具来生成uImage   (u-boot源码包/tools/mkimage.c )

这里解释一下参数的意义:

-A ==> set architecture to 'arch'

-O ==> set operating system to 'os'

-T ==> set image type to 'type' “kernel或是ramdisk”

-C ==> set compression type 'comp'

-a ==> set load address to 'addr' (hex)

-e ==> set entry point to 'ep' (hex)(内核启动时在此位置查询完整的内核印象)

-n ==> set image name to 'name'

-d ==> use image data from 'datafile'

-x ==> set XIP (execute in place,即不进行文件的拷贝,在当前位置执行)

2015-11-08 19:34:46 BusyLuo 阅读数 958
  • C语言嵌入式Linux编程第3期:程序的编译、链接和运行

    本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。

    1125 人正在学习 去看看 王利涛

    

    之前学习完了u-boot的启动,本文主要讲u-boot为linux内核的运行做了哪些准备工作。总结u-boot的作用,主要就是初始化硬件设备、写入linux的启动参数以及加载linux内核。下面对这几个主要作用作说明。


1.初始化(TQ210开发板)

首先了解在u-boot启动前,BL0完成的工作:

1禁止看门狗
2.初始化指令cache
3.
初始化栈、堆
4.初始化块设备拷贝函数
5.初始化PLL( 锁相环)、设置系统时钟
6.根据OM引脚配置,从指定的外部存储器拷贝BL1到内部SRAM
7. 校验BL1的校验和


BL1(u-boot-spl) 中初始化时钟、DRAM控制器。


2.写入linux启动参数

    u-boot给linux参数是通过将参数以约定的格式放在内存中,然后u-boot将这个存放这个参数的内存地址传递给linux内核。

    u-boot启动后,会自动执行其环境变量里bootcmd代表的命令,在执行这个命令前,可以添加一个延时,在这段时间内可以通过设定的按键来中止自动执行bootcmd,这个延时的长短由CONFIG_BOOTDELAY决定,但也可以设置环境变量来改变它(对于其它环境变量也是这样)。当CONFIG_BOOTDELAY等于0时,可以通过定义CONFIG_ZERO_BOOTDELAY_CHECK来中止自动执行过程,但按键必须在启动前按下。

    u-boot传递给linux的参数是通过bootargs来传递的,在执行bootm前必须设置好bootargs,然后在bootcmd里添加bootm命令。

    bootm的工作是根据bootargs写入参数并将linux内核拷贝到运行的位置。写入的格式参照3.2.4 设置内核的启动参数》 章节和U-Boot与Linux内核的交互,写入的位置在内存的0-0x4000这个范围内,0x4000-0x8000存放的是内核页表,这些地址在内核中有相关的宏定义,这也是为什么内核的加载地址一般为0x****8000的原因。在参数写入完成后,将寄存器R0赋值为0,R1赋值为机器号, R2赋值为启动参数数据结构的首地址。


3.加载linux内核

    调用bootm命令时,需要传入一个参数,这个参数代表的是将内核放到什么地方,那从什么地方去读内核文件呢?是通过u-boot分区别表,简单的说就是u-boot指定一块存储空间为kernel分区,并把内核映像放到该分区里,加载的时候就直接从这里读取了,详细请看http://blog.sina.com.cn/s/blog_95268f5001013e50.html。

    找到内核映像(uImage)后,读取它的前0x40个字节,得到loadaddr 和entry point两个地址,这两个地址是在制作uImage时指定的,u-boot会对这几个地址作判断。最后跳转到linux处,将控制权移交给linux。


        



           

2014-03-05 19:20:03 njuitjf 阅读数 10339
  • C语言嵌入式Linux编程第3期:程序的编译、链接和运行

    本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。

    1125 人正在学习 去看看 王利涛

Linux u-boot加载过程
----基于freescale i.MX6

近期做项目需要修改u-boot,刚好趁机研究一把linux u-boot。

以前没接触过u-boot,所以学习只能从头开始。
既然是从头开始, 那就从u-boot的加载过程起步吧。

本文主要包括以下内容:
1、cpu如何开始加载u-boot。
2、加载u-boot时的初期处理。
3、如何跳转到c代码。
c代码中的处理过程,以及后续处理,本文不作介绍。

做我们这个行当的,都知道程序需要跑在内存上(能够XIP的flash除外,此处不作介绍)。
但刚上电时,cpu并不知道它连了些什么东西,甚至连没连接内存也不知道,另外想在内存上执行,也总要把程序加载到内存才行。
如此看来,机器上电的处理比想象的要复杂不少!习惯了开机只是按下开关,想不到还有这么多东东隐藏在背后!!!
那么,程序到底是如何开始跑的呢?
启动过程一般在cpu的参考手册中都有介绍,我这边就不作详细介绍,但相关的部分,会稍微提一下。
上电后,cpu内部会做一些检查,如电压是否稳定,启动模式等等,之后会从地址0x00000000开始执行命令。
从地址0x00000000开始,一般是cpu的rom code,也就是cpu芯片上写死的。
rom code开始执行,会作很多处理,会处理什么呢?cpu厂商很清楚,呵呵。
其实,多数处理可以看看参考手册中的启动时序。
我们关心的:根据启动模式,确定启动设备;然后对启动设备进行验证,并进行初步初始化。
(也就是参考手册中有很多地方会说的,在启动过程中是什么样子,例如始终是 XX M,启动过程之后是另外一个样子,例如时钟变为 YY M)
初始化好了,然后就是去加载我们的程序了,也就是从启动设备上读取我们的程序。
从哪儿开始读取呢,当然是有道道的。参考手册中一般会指定各种启动设备中程序存放的地址。如i.MX6中,若从emmc启动,会从emmc的0x400 bytes地址开始读取程序(1K bytes的地方).
读过来的是个什么东东呢?直接可以跑的程序?
不是,参考手册中有说明。
i.MX6中读到的是一个IVT(image vector table)结构体,结构体的定义肯定是规定好的,不然rom code怎么知道如何解析该结构体?
i.MX6中对IVT结构体的定义:
header
entry: Absolute address of the first instruction to execute from the image
reserved1: Reserved and should be zero
dcd: Absolute address of the image DCD. The DCD is optional so this field may be set to NULL if no DCD is required. See
Device Configuration Data (DCD) for further details on DCD.
boot data: Absolute address of the Boot Data
self: Absolute address of the IVT. Used internally by the ROM
csf: Absolute address of Command Sequence File (CSF) used by the HAB library. See High Assurance Boot (HAB) for
details on secure boot using HAB. This field must be set to NULL when not performing a secure boot
reserved2: Reserved and should be zero

我们关系以下几个:
entry: 入口函数,在程序中指定,如:ENTRY(_start)
dcd: Device Configuration Data, 设备配置数据,用来配置设备的。因为刚上电,设备的参数都是默认的,不能达到最优效果,甚至有些设备不进行初始化根本就无法工作。
那怎么办呢?就需要在使用这些设备之前,进行一些设置,如内存等等。
dcd中可以设置如下这些东东:
Address range                                      Start address       Last Address
IOMUX Control (IOMUXC) registers     0x020E0000        0x020E3FFF
CCM register set                                   0x020C4000        0x020C7FFF
ANADIG registers                                 0x020C8000        0x020C8FFF
MMDC register set                                0x021B0000        0x021B7FFF
IRAM Free Space                                 0x00907000        0x00937FF0
EIM                                                       0x08000000        0x0FFEFFFF
DDR                                                      0x10000000        0xFFFFFFFF
DCD中命令的格式这儿就不介绍了,参考手册中有详细说明。

来看看IVT定义的例子吧:

.section ".text.flasheader", "x"
 b _start
 .org CONFIG_FLASH_HEADER_OFFSET
 
ivt_header:       .word 0x402000D1 /* Tag=0xD1, Len=0x0020, Ver=0x40 */
app_code_jump_v:  .word _start
reserv1:          .word 0x0
dcd_ptr:          .word dcd_hdr
boot_data_ptr:   .word boot_data
self_ptr:         .word ivt_header
app_code_csf:     .word 0x0
reserv2:          .word 0x0



在该例子中,入口函数为_start,在start.S中定义;dcd_hdr 指向dcd image,在flashheader.S中定义;IVT结构体也定义在flashheader.S中;
boot_data是个结构体,定义在flashheader.S中:

boot_data:        .word TEXT_BASE
image_len:        .word _end_of_copy  - TEXT_BASE + CONFIG_FLASH_HEADER_OFFSET
plugin:           .word 0x0



TEXT_BASE定义在config.mk中:

 TEXT_BASE = 0x27800000



CONFIG_FLASH_HEADER_OFFSET,看字面意思,就是flash header的偏移地址,flash header? 我们前面有个flashheader.S难道就是这个?
看了下其定义:

 #define CONFIG_FLASH_HEADER_OFFSET 0x400



原来就是flash header在启动设备上的偏移地址,也就IVT结构体在启动设备中的偏移。
_end_of_copy的定义在u-boot.lds中定义,应该就是u-boot结束的地方。
image_len为 _end_of_copy  - TEXT_BASE还可以理解,为什还要加个CONFIG_FLASH_HEADER_OFFSET?
难道是rom code是按照image len从启动设备的开始地址加载u-boot,所以要包含CONFIG_FLASH_HEADER_OFFSET??
另外在.text.flasheader段定义的地方有下面一行代码:

 .org CONFIG_FLASH_HEADER_OFFSET



难道是.text.flasheader段的头部故意空出来了CONFIG_FLASH_HEADER_OFFSET个bytes的空间?
看了下System.map,有如下:

27800400 t ivt_header



其中27800000是u-boot加载地址,而IVT开始的地方,即ivt_header在u-boot加载地址偏移CONFIG_FLASH_HEADER_OFFSET(0x400)的地方。
注意,这儿说的是加载地址,也就是内存中的地址,不是在启动设备上存储的地址。

知道了IVT的定义,并且知道rom code是从启动设备的固定地址来加载IVT,那么是如何将IVT放到启动设备的固定地址的呢?
我们知道,u-boot是被写到了启动设备的固定地址,也就是说,只要IVT结构体在u-boot的头部,就能够保证rom code能够加载到IVT。
的确是这样的,IVT在flashheader.S的头部,而flashheader.o又在u-boot的头部,所以IVT就在u-boot的头部。
这是通过u-boot.lds来实现的:
(u-boot.lds文件不长,干脆全列出来吧)

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
 . = 0x00000000;

 . = ALIGN(4);
 .text    :
 {
   /* WARNING - the following is hand-optimized to fit within */
   /* the sector layout of our flash chips! XXX FIXME XXX */
   board/freescale/mx6dl_14cytmap/flash_header.o (.text.flasheader)
   cpu/arm_cortexa8/start.o
   drivers/mmc/libmmc.a  (.text)
   /* 其他 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 = .;

 . = ALIGN(4);
 _end_of_copy = .; /* end_of ROM copy code here */
 __bss_start = .;
 .bss : { *(.bss) }
 _end = .;
}



从u-boot.lds可知,flashheader.o在代码段的首部,并且代码段在最前面,因此就保证了前面所说的IVT在启动设备上的固定位置。

rom code最初加载了哪些东东呢?
rom code根据启动设备的偏移地址,首先把uboot加载到iRame。
然后开始解析IVT结构体。
之后找到DCD所在的地址(在IVT)中,逐条执行DCD命令。主要是初始化内存等。
之后呢,找到entry函数,也就是IVT结构体中的entry成员,对应到上面的例子是_start函数,开始执行。
_start函数中start.S中定义:

.globl _start
_start: 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
 ldr pc, _fiq
 ...



 
可见_start的第一句就跳转到了reset。之后是一些异常处理的东东。
再看看reset,也在start.S中。
reset的名称是自定义的,有些地方称为start_code。
reset主要完成以下工作:
1、set the cpu to SVC32 mode
2、初始化cpu的一些东东,如果需要的话
3、如果有必要,重新定位u-boot。其实就是判断启动地址,即_start的地址,与加载地址,即TEXT_BASE是否相同,如果不同,则将uboot从启动地址copy到加载地址。
4、设置stack、bss、irq、fiq,其实就是在内存中留出来一些空间。
5、看是否需要配置MMU。
6、最后:

 ldr pc, _start_armboot @ jump to C code



 将_start_armboot加载到pc寄存器,pc寄存器保存的是吓一条指令的地址,所以也就是跳到_start_armboot执行。
_start_armboot定义:

_start_armboot: .word start_armboot



start_armboot 是c代码中定义的函数。在代码Board.c中定义。
终于从汇编出来了。

 

2014-05-22 19:33:17 chunlovenan 阅读数 1057
  • C语言嵌入式Linux编程第3期:程序的编译、链接和运行

    本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。

    1125 人正在学习 去看看 王利涛

前言;下面将从下面几个方面来介绍u-boot :目录

一:什么是u-boot、u-boot的工作模式和uboot的功能介绍

二;u-boot的编译体验

三;u-boot的基本命令

四;u-boot的代码目录结构

五:u-boot工作流程


一:什么是u-boot、u-boot的工作模式和简单介绍

从软件角度来看,一个嵌入式系统可以分为四个层次:

(1):引导加载程序:包括固件(firmware)中的boot程序(可选)和bootloader两大部分,相当于PC机中的bios和GRUB或LILO。引导程序的主要任务是将内核从硬盘上读到内存中,然后跳转到内核的入口地址处去执行,即启动操作系统,然后u-boot就是bootloader中的一种。

(2):Linux内核:特定于嵌入式平台的定制内核。

(3):文件系统:包括了系统命令和应用程序。

(4):用户应用程序:特定于用户的应用程序。

下面就是固态储存设备的空间分配结构图:


如图所示:lootloader是位于整个flash中的最前端部分,接着是boot的参数配置,然后就是内核部分,节奏就是跟文件系统;



u-boot的工作模式有:启动加载模式和下载模式;

u-boot支持多种嵌入式操作系统,如Linux、NETBSD、Vxwork、QNX、RTEMS、ARTOS、Lynx0S.

支持多个处理器系列,如PowerPC 、ARM、x86、MIPS、Xscale

丰富的设备驱动源代码,如串口。以太网、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、键盘等。


U-boot的主要功能:

作为一个较复杂的bootloader来说,其实u-boot的功能已经称得上一个小的微内核。如果配上一些后续的进程(或线程管理),加上一些具体设备的驱动,就能基本实现一个小的嵌入式操作系统。其只要功能如下:

(1):系统引导功能:

         支持NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统;支持NFS挂载、从FLASH中引导压缩或非压缩系统内核;

(2):基本辅助功能:

        强大的操作系统接口功能,可灵活设置和传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布, 尤其对Linux的支持最为强劲;支持目标板环境参数多种存储方式,如flash .NVflash、EEPROM。

(3):CRC32校验:

      可校验FLASH中的内核、RAMDISK镜像文件是否完好。

(4):设备驱动:

      串口、SDRAM、FLASH、以太网、LCD、NVRAM、键盘、USB、PCMCIA、PCI、RTC等的支持。

(5):上电自检功能:

      SDRAM、FLASH大小自动检测;SDRAM故障检测;CPU型号。

(6):特殊功能:XIP内核引导


二、u-boot的编译体验:

注意本实验平台:交叉工具链是:arm-linux-gcc-3.4.5版本的。u-boot是U-boot-1.1.6版本的。

编译步骤:

(一):解压u-boot大源码包   Tar xjf  u-boot-1.1.6...... 

(二):打补丁;  patch -p1 <  .. /u-boot-1.1.6_jz2440.patch

(三):配置配置成适合自己的单板   make 100ask24x0_config

(四):编译 :  make

编译结果是:生成一个194KB的u-boot.bin,即可移植到flash上去运行。



三;u-boot的基本命令

(1):寻求帮助命令;help(?:help的别名) (h:查询一个命令的缩写)

(2):环境变量相关指令:  printenv  、setenv    注意:环境变量时指操作系统中用来指定操作系统运行环境的一些参数。

(3):文件下载相关命令:  tftp ,串口下载相关命令:loadb

(4):内存操作相关命令:显示内存值md[.b、.w、.i]      修改内存:mm[.b、.w、.i]   

(5):Flash相关命令:flinfo 、 protect  off/on all、erase  、cp。

(6):执行程序相关命令:go  、bootm、run

(7):其他命令;bdinfo、bootcmd

 
四:u-boot的代码目录结构:

1、board中存放于开发板相关的配置文件,每一个开发板都以子文件夹的形式出现。

2、Commom文件夹实现u-boot行下支持的命令,每一个命令对应一个文件。

3、cpu中存放特定cpu架构相关的目录,每一款cpu架构都对应了一个子目录。

4、Doc是文档目录,有u-boot非常完善的文档。

5、Drivers中是u-boot支持的各种设备的驱动程序。

6、Fs是支持的文件系统,其中最常用的是JFFS2文件系统。

7、Include文件夹是u-boot使用的头文件,还有各种硬件平台支持的汇编文件,系统配置文件和文件系统支持的文件。

8、Net是与网络协议相关的代码,bootp协议、TFTP协议、NFS文件系统得实现。

9、Tooles是生成U-boot的工具。 

10、disk硬件接口程序

11、dtt数字温度测量器或者温度传感器的驱动

12、lib_generic通用库函数的实现

13、lib_ppc存放对PowerPC体系结构通用的文件

14、存放对ARM体系结构通用的文件


总结:这些目录中所要存放的文件有着一定规则,基本上可以分为3类:

(1):与处理器体系结构或者开发板硬件直接相关

(2):一些通用的函数或者驱动

(3):U-boot的应用程序、工具或者文档


五:u-boot工作流程





2018-08-29 14:09:04 W1107101310 阅读数 112
  • C语言嵌入式Linux编程第3期:程序的编译、链接和运行

    本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。

    1125 人正在学习 去看看 王利涛

简介:

        本文主要介绍在u-boot-1.1.6中代码的运行过程,以此来了解在u-boot中如何实现引导并启动内核。这里我们主要介绍u-boot第四阶段的代码,即讲解的是在u-boot中我们如何运用命令行来加载并启动内核。

声明:

        本文主要是看了韦东山老师的视频后所写,希望对你有所帮助。

u-boot版本 : u-boot-1.1.6

开发板 : JZ2440

Linux内核 : Linux-2.6.22.6

bootm : 命令行启动内核

        我在上面一篇文章:嵌入式Linux——分析u-boot运行过程(3):u-boot第三阶段代码中讲解了u-boot通过U_BOOT_CMD宏将各种命令放到命令列表中,而在本章中我们要讲的是如何通过命令行来启动内核,这个命令名为:bootm 。同时我们在u-boot中搜"bootm"可以找到其对应的命令定义。他的代码为:

#define	CFG_MAXARGS		16		/* max number of command args	*/

U_BOOT_CMD(
 	bootm,	CFG_MAXARGS,	1,	do_bootm,
 	"bootm   - boot application image from memory\n",
 	"[addr [arg ...]]\n    - boot application image stored in memory\n"
 	"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
 	"\t'arg' can be the address of an initrd image\n"
);

        通过前面我们对U_BOOT_CMD的介绍大家可以知道,上面命令的名称为bootm,他的参数个数最大为16,而他是不可重复的。同时我们还可以知道这个命令的操作函数为do_bootm,而下面的几行字符则是bootm命令的帮助信息。好了,解释到这里我想大家都明白了,这个命令的重点是操作函数:do_bootm。所以我们直接分析do_bootm函数:

#define	CFG_LOAD_ADDR		0x33000000	/* default load address	*/

ulong load_addr = CFG_LOAD_ADDR;		/* Default Load Address */

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	ulong	iflag;
	ulong	addr;
	ulong	data, len, checksum;
	ulong  *len_ptr;
	uint	unc_len = CFG_BOOTM_LEN;
	int	i, verify;
	char	*name, *s;
	int	(*appl)(int, char *[]);
	image_header_t *hdr = &header;

	s = getenv ("verify");
	verify = (s && (*s == 'n')) ? 0 : 1;

	if (argc < 2) {
		addr = load_addr;
	} else {
		addr = simple_strtoul(argv[1], NULL, 16);
	}

	SHOW_BOOT_PROGRESS (1);
	printf ("## Booting image at %08lx ...\n", addr);

	/* Copy header so we can blank CRC field for re-calculation */
	memmove (&header, (char *)addr, sizeof(image_header_t));

	if (ntohl(hdr->ih_magic) != IH_MAGIC) {
	    {
		puts ("Bad Magic Number\n");
		SHOW_BOOT_PROGRESS (-1);
		return 1;
	    }
	}
	SHOW_BOOT_PROGRESS (2);

	data = (ulong)&header;
	len  = sizeof(image_header_t);

	checksum = ntohl(hdr->ih_hcrc);
	hdr->ih_hcrc = 0;

	if (crc32 (0, (uchar *)data, len) != checksum) {
		puts ("Bad Header Checksum\n");
		SHOW_BOOT_PROGRESS (-2);
		return 1;
	}
	SHOW_BOOT_PROGRESS (3);


	/* for multi-file images we need the data part, too */
	print_image_hdr ((image_header_t *)addr);

	data = addr + sizeof(image_header_t);
	len  = ntohl(hdr->ih_size);

	if (verify) {
		puts ("   Verifying Checksum ... ");
		if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
			printf ("Bad Data CRC\n");
			SHOW_BOOT_PROGRESS (-3);
			return 1;
		}
		puts ("OK\n");
	}
	SHOW_BOOT_PROGRESS (4);

	len_ptr = (ulong *)data;

	if (hdr->ih_arch != IH_CPU_ARM)
	{
		printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
		SHOW_BOOT_PROGRESS (-4);
		return 1;
	}
	SHOW_BOOT_PROGRESS (5);

	switch (hdr->ih_type) {
	case IH_TYPE_STANDALONE:
		name = "Standalone Application";
		/* A second argument overwrites the load address */
		if (argc > 2) {
			hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
		}
		break;
	case IH_TYPE_KERNEL:
		name = "Kernel Image";
		break;
	case IH_TYPE_MULTI:
		name = "Multi-File Image";
		len  = ntohl(len_ptr[0]);
		/* OS kernel is always the first image */
		data += 8; /* kernel_len + terminator */
		for (i=1; len_ptr[i]; ++i)
			data += 4;
		break;
	default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
		SHOW_BOOT_PROGRESS (-5);
		return 1;
	}
	SHOW_BOOT_PROGRESS (6);

	/*
	 * We have reached the point of no return: we are going to
	 * overwrite all exception vector code, so we cannot easily
	 * recover from any failures any more...
	 */

	iflag = disable_interrupts();

	switch (hdr->ih_comp) {
	case IH_COMP_NONE:
		if(ntohl(hdr->ih_load) == data) {
			printf ("   XIP %s ... ", name);
		} else {
			memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
		}
		break;
	case IH_COMP_GZIP:
		printf ("   Uncompressing %s ... ", name);
		if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
			    (uchar *)data, &len) != 0) {
			puts ("GUNZIP ERROR - must RESET board to recover\n");
			SHOW_BOOT_PROGRESS (-6);
			do_reset (cmdtp, flag, argc, argv);
		}
		break;

	default:
		if (iflag)
			enable_interrupts();
		printf ("Unimplemented compression type %d\n", hdr->ih_comp);
		SHOW_BOOT_PROGRESS (-7);
		return 1;
	}
	puts ("OK\n");
	SHOW_BOOT_PROGRESS (7);

	switch (hdr->ih_type) {
	case IH_TYPE_STANDALONE:
		if (iflag)
			enable_interrupts();

		/* load (and uncompress), but don't start if "autostart"
		 * is set to "no"
		 */
		if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {
			char buf[32];
			sprintf(buf, "%lX", len);
			setenv("filesize", buf);
			return 0;
		}
		appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);
		(*appl)(argc-1, &argv[1]);
		return 0;
	case IH_TYPE_KERNEL:
	case IH_TYPE_MULTI:
		/* handled below */
		break;
	default:
		if (iflag)
			enable_interrupts();
		printf ("Can't boot image type %d\n", hdr->ih_type);
		SHOW_BOOT_PROGRESS (-8);
		return 1;
	}
	SHOW_BOOT_PROGRESS (8);

	switch (hdr->ih_os) {
	default:			/* handled by (original) Linux case */
	case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
	    fixup_silent_linux();
#endif
	    do_bootm_linux  (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;
	case IH_OS_NETBSD:
	    do_bootm_netbsd (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;

	case IH_OS_RTEMS:
	    do_bootm_rtems (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;

	}

	SHOW_BOOT_PROGRESS (-9);
	return 1;
}

        分析上面程序,我们可以将上面程序所做的事情分为以下几块:

1.   检测参数个数或确定加载地址
2.   将内核映射的头部拷贝到header
3.   检测uImage的头部参数ih_magic是否正确
4.   检测头部的CRC校验值是否正确
5.   对于多文件的image,需要检测镜像的CRC值
6.   检验uImage的架构是否为arm架构
7.   检测uImage的镜像类型
8.   关中断
9.   检测uImage的压缩类型
10. 检测操作系统类型,针对不同的类型有不同的启动方式

        而对于上面的不同步骤,我们可以发现其实他们就是:用不同的方式来检验uImage头部64字节的信息。因此我们知道上面函数的重点就是uImage头部的信息了。在介绍uImage这64字节头部信息之前,我想先为大家讲解一下这64字节数据的由来。大家应该知道内核在编译成功后会生成image和zimage两种文件,他们分别为内核映像文件和内核映像压缩文件,一般image文件的大小为4M左右,而zimage的大小为2M左右。所以引入压缩文件可以节省很多的空间。但u-boot在加载zimage的时候还需要获得关于加载地址,入口地址,操作系统类型以及最重要的解压方式等的一些参数,而这些数据的传输使得程序变得十分复杂。而这个时候 Legacy uImage的64字节头部信息的引入就为u-boot加载内核提供了足够的信息。因此关于头部信息的检测对于加载和启动内核是个很重要的过程。下面我们来看uImage的头部都包含哪些信息:

typedef struct image_header {
	uint32_t	ih_magic;	/* 幻数头,用来校验是否是一个Legacy-uImage */
	uint32_t	ih_hcrc;	/* 头部的CRC校验值	*/
	uint32_t	ih_time;	/* 镜像创建的时间戳	*/
	uint32_t	ih_size;	/* 镜像数据长度 */
	uint32_t	ih_load;	/* 加载地址 */
	uint32_t	ih_ep;		/* 入口地址 */
	uint32_t	ih_dcrc;	/* 镜像的CRC校验 */
	uint8_t		ih_os;		/*  操作系统类型 */
	uint8_t		ih_arch;	/* CPU架构		*/
	uint8_t		ih_type;	/* 镜像类型 */
	uint8_t		ih_comp;	/* 压缩类型		*/
	uint8_t		ih_name[IH_NMLEN];	/* 镜像名 */
} image_header_t;

        了解完头部的信息后,下面我们就要结合着头部的信息来看do_bootm函数中到底是如何对头部信息进行检测的,同时我们也可以结合do_bootm的代码对头部的信息有一个更加深入的了解。,例如不同的压缩类型,不同操作系统类型,不同的CPU架构等。

检测参数个数或确定加载地址:

        我在上一篇文章中讲到在命令行中可以敲入字符串命令来控制单板做一些特定的事情,而命令要通过空格或者制表符来分离为几个分离的字符串,而这几个字符串就是该命令的参数,在u-boot中我们可以使用单独的bootm命令来启动内核,也可以通过“bootm  内核加载地址”的方式来启动内核。因此程序要在这里检验参数的个数是否小于2,如果参数为1(参数不可能为0,为0就进不了bootm命令了),这说明在命令行中只输入了字符串“bootm”,那么内核的加载地址就是用默认的参数。而如果命令行的参数个数大于等于2,那么第二个参数就是加载地址了(对于bootm命令有一定的命令规范第二个参数只能是加载地址),这时候程序将我们键入的字符型加载地址转化为长整型数字并赋值给变量addr。同时需要强调一点的是:这里的加载地址也是uImage头部的首地址。该步骤的操作代码为:

	if (argc < 2) {
		addr = load_addr;
	} else {
		addr = simple_strtoul(argv[1], NULL, 16);
	}

	SHOW_BOOT_PROGRESS (1);
	printf ("## Booting image at %08lx ...\n", addr);

将内核映射的头部数据拷贝到header:

        其实将内核头部数据拷贝到header位置的代码还是很简单的,就是使用memmove函数将源地址的内容拷贝指定长度到目的地址。他的代码为

	memmove (&header, (char *)addr, sizeof(image_header_t));

        在上面的代码中源为addr(内核头部首地址),目的地址为header的地址,而拷贝的长度为image_header_t结构体的数据长度。

检测uImage的头部参数ih_magic是否正确:

        这步要检验的是ih_magic参数(幻数头),用来校验uImage是否是一个Legacy-uImage,在前面我们已经介绍了Legacy-uImage可以有效的帮助u-boot加载并启动内核,而Legacy-uImage印象文件有他自己特有的幻数头,因此我们可以用幻数头来检验这个image是否为一个uImage。对于本u-boot来说幻数头为:

#define IH_MAGIC	0x27051956	/* Image Magic Number		*/

        而检验幻数头的代码为:

	if (ntohl(hdr->ih_magic) != IH_MAGIC) {
		puts ("Bad Magic Number\n");
		SHOW_BOOT_PROGRESS (-1);
		return 1;
	}
	SHOW_BOOT_PROGRESS (2);

检测头部的CRC校验值是否正确:

        这里需要计算头部信息的CRC校验值,并用这个CRC校验值与存储在头部的CRC校验值进行比较,如果相同则表示正确程序继续向下运行。如果不同则表示错误,这时do_bootm函数将终止执行。而计算头部的CRC校验值需要uImage头部信息的首地址以及image_header_t结构体的大小(即64字节)。而在本次检验中使用了CRC32算法来生成校验码。而关于CRC32算法的详细解释大家可以看:crc32 算法与实现 。而关于头部的CRC检验代码为:

	data = (ulong)&header;
	len  = sizeof(image_header_t);

	checksum = ntohl(hdr->ih_hcrc);
	hdr->ih_hcrc = 0;

	if (crc32 (0, (uchar *)data, len) != checksum) {
		puts ("Bad Header Checksum\n");
		SHOW_BOOT_PROGRESS (-2);
		return 1;
	}
	SHOW_BOOT_PROGRESS (3);

对于多文件的image,需要检测镜像的CRC值:

        对于多文件的image,除了头部我们还要对uImage的数据部分做CRC检验,程序与上面相似。而不同的地方是数据开始位置变为了真正内核数据开始的位置即uImage头部信息的首地址加上头部所占的64字节空间。同时这里的数据大小变为了内核数据的大小。他的实现代码为:

	/* for multi-file images we need the data part, too */
	print_image_hdr ((image_header_t *)addr);

	data = addr + sizeof(image_header_t);
	len  = ntohl(hdr->ih_size);

	if (verify) {
		puts ("   Verifying Checksum ... ");
		if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
			printf ("Bad Data CRC\n");
			SHOW_BOOT_PROGRESS (-3);
			return 1;
		}
		puts ("OK\n");
	}
	SHOW_BOOT_PROGRESS (4);

检验uImage的架构是否为arm架构:

        下面就要检测uImage是否为arm架构的CPU所编译。如果是则进行下一步,而如果不是就要检测是否为其他的架构所编译,对于不同架构的cpu会有不同的uImage。而对于我们的开发板而言,如果没有找到为arm架构所编译的uImage,那么程序运行到这里就会返回而不去执行下一步的操作。对于arm架构CPU他的定义为:

#define IH_CPU_ARM		2	/* ARM		*/

        而检验uImage架构的代码为:

	if (hdr->ih_arch != IH_CPU_ARM)
	{
		printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
		SHOW_BOOT_PROGRESS (-4);
		return 1;
	}
	SHOW_BOOT_PROGRESS (5);

检测uImage的镜像类型:

        下面程序就要检验uImage的镜像类型了,这里其实就是查看uImage中的真正数据是由什么组成的。而对于我们开发板上的uImage是由kernel编译生成的。所以我们看代码:

	switch (hdr->ih_type) {
	case IH_TYPE_STANDALONE:
		name = "Standalone Application";
		/* A second argument overwrites the load address */
		if (argc > 2) {
			hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
		}
		break;
	case IH_TYPE_KERNEL:
		name = "Kernel Image";
		break;
	case IH_TYPE_MULTI:
		name = "Multi-File Image";
		len  = ntohl(len_ptr[0]);
		/* OS kernel is always the first image */
		data += 8; /* kernel_len + terminator */
		for (i=1; len_ptr[i]; ++i)
			data += 4;
		break;
	default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
		SHOW_BOOT_PROGRESS (-5);
		return 1;
	}
	SHOW_BOOT_PROGRESS (6);

        因为我们知道开发板上的uImage是由kernel生成,所以从上面的程序中我们知道 name = "Kernel Image";

关中断:

        在这里关闭中断其实就是通过关闭中断来防止因为某种意想不到的中断发生来打断下面的解压过程(下面的解压过程是不可被打断的)。而关中断的代码其实就是用在C语言中嵌入汇编代码的方式来操作当前程序状态寄存器来关中断。关中断的代码为:

/*
 * disable IRQ/FIQ interrupts
 * returns true if interrupts had been enabled before we disabled them
 */
int disable_interrupts (void)
{
	unsigned long old,temp;
	__asm__ __volatile__("mrs %0, cpsr\n"
			     "orr %1, %0, #0xc0\n"
			     "msr cpsr_c, %1"
			     : "=r" (old), "=r" (temp)
			     :
			     : "memory");
	return (old & 0x80) == 0;
}

 检测uImage的压缩类型:

        通过检验不同的压缩类型进而根据这些压缩类型来找到对应的解压函数,来将映像压缩文件转化为内核的真正代码。在uImage中我们使用gzip的方式来对内核文件进行压缩操作。所以u-boot使用gunzip函数来为内核解压,检验压缩方式并针对不同方式进行解压的代码为:

	switch (hdr->ih_comp) {
	case IH_COMP_NONE:
		if(ntohl(hdr->ih_load) == addr) {
			printf ("   XIP %s ... ", name);
		} else {
			size_t l = len;
			void *to = (void *)ntohl(hdr->ih_load);
			void *from = (void *)data;

			printf ("   Loading %s ... ", name);

			while (l > 0) {
				size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
				WATCHDOG_RESET();
				memmove (to, from, tail);
				to += tail;
				from += tail;
				l -= tail;
			}
		}
		break;
	case IH_COMP_GZIP:
		printf ("   Uncompressing %s ... ", name);
		if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
			    (uchar *)data, &len) != 0) {
			puts ("GUNZIP ERROR - must RESET board to recover\n");
			SHOW_BOOT_PROGRESS (-6);
			do_reset (cmdtp, flag, argc, argv);
		}
		break;
	default:
		if (iflag)
			enable_interrupts();
		printf ("Unimplemented compression type %d\n", hdr->ih_comp);
		SHOW_BOOT_PROGRESS (-7);
		return 1;
	}
	puts ("OK\n");
	SHOW_BOOT_PROGRESS (7);

        从上面的代码看,如果程序在解压的过程中失败,即gunzip函数返回非0值时,u-boot会放弃当前操作并重启u-boot。


检测操作系统类型,针对不同的类型有不同的启动方式:

        根据不同的操作系统类型u-boot有不同的启动函数,而不同的启动函数对应不同的启动方式。因为我们的uImage是由Linux的代码生成,所以我们会在下面的代码中选择Linux系统,然后使用do_bootm_linux函数来加载和启动内核。他的代码为:

	switch (hdr->ih_os) {
	default:			/* handled by (original) Linux case */
	case IH_OS_LINUX:

	    do_bootm_linux  (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;
	case IH_OS_NETBSD:
	    do_bootm_netbsd (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;


	case IH_OS_RTEMS:
	    do_bootm_rtems (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;

	}

        讲到这里关于uImage头部信息的检测就讲完了。如果上面的这些检测都没有问题,那么下一步我们就要进入do_bootm_linux函数来了解真正的加载和启动内核了。

        我想大家在看上面的代码的时候会发现在每一步完成之后都会有一个SHOW_BOOT_PROGRESS 函数,而该函数真正的功能是什么那?我们可以看下他的代码:


# define SHOW_BOOT_PROGRESS(arg)	show_boot_progress(arg)

void show_boot_progress (int status)
{
	switch (status) {
	case 1:
		stamp_led_set (STATUS_LED_OFF, STATUS_LED_OFF, STATUS_LED_ON);
		break;
	case 2:
		stamp_led_set (STATUS_LED_OFF, STATUS_LED_ON, STATUS_LED_OFF);
		break;
	case 3:
		stamp_led_set (STATUS_LED_OFF, STATUS_LED_ON, STATUS_LED_ON);
		break;
	case 4:
		stamp_led_set (STATUS_LED_ON, STATUS_LED_OFF, STATUS_LED_OFF);
		break;
	case 5:
	case 6:
		stamp_led_set (STATUS_LED_ON, STATUS_LED_OFF, STATUS_LED_ON);
		break;
	case 7:
	case 8:
		stamp_led_set (STATUS_LED_ON, STATUS_LED_ON, STATUS_LED_OFF);
		break;
	case 9:
	case 10:
	case 11:
	case 12:
	case 13:
	case 14:
	case 15:
		stamp_led_set (STATUS_LED_OFF, STATUS_LED_OFF,
			       STATUS_LED_OFF);
		break;
	default:
		stamp_led_set (STATUS_LED_ON, STATUS_LED_ON, STATUS_LED_ON);
		break;
	}
}

        从上面的代码中我们可以看出,其实SHOW_BOOT_PROGRESS函数所做的工作就是通过判断不同的输入参数来控制不同的led的亮灭,依次来为我们显示程序运行进度。同时如果程序在那部分出现了错误也可以通过查看LED的亮灭来确定程序是在那部分出现了错误。因此SHOW_BOOT_PROGRESS函数在这里起到一个进度提示的作用。

加载并启动内核:

        做完上面对于uImage头部的检测工作之后,下面我们就要介绍如何加载并启动内核了,可以说前面的检测是一个准备工作,只有前面的工作做好了,做正确了,这一步加载和启动内核的程序才能正常运行。同时你会发现do_bootm_linux函数的很多输入参数都是由do_bootm函数所传入。好了,我们先全面的看一下do_bootm_linux函数,而我会在后面再分开详细的讲解他的组成。do_bootm_linux函数的代码为:

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
		     ulong addr, ulong *len_ptr, int verify)
{
	ulong len = 0, checksum;
	ulong initrd_start, initrd_end;
	ulong data;
	void (*theKernel)(int zero, int arch, uint params);
	image_header_t *hdr = &header;
	bd_t *bd = gd->bd;

#ifdef CONFIG_CMDLINE_TAG
	char *commandline = getenv ("bootargs");
#endif

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

	/*
	 * Check if there is an initrd image
	 */
	if (argc >= 3) {
		SHOW_BOOT_PROGRESS (9);

		addr = simple_strtoul (argv[2], NULL, 16);

		printf ("## Loading Ramdisk Image at %08lx ...\n", addr);

		/* Copy header so we can blank CRC field for re-calculation */

		memcpy (&header, (char *) addr,
				sizeof (image_header_t));

		if (ntohl (hdr->ih_magic) != IH_MAGIC) {
			printf ("Bad Magic Number\n");
			SHOW_BOOT_PROGRESS (-10);
			do_reset (cmdtp, flag, argc, argv);
		}

		data = (ulong) & header;
		len = sizeof (image_header_t);

		checksum = ntohl (hdr->ih_hcrc);
		hdr->ih_hcrc = 0;

		if (crc32 (0, (unsigned char *) data, len) != checksum) {
			printf ("Bad Header Checksum\n");
			SHOW_BOOT_PROGRESS (-11);
			do_reset (cmdtp, flag, argc, argv);
		}

		SHOW_BOOT_PROGRESS (10);

		print_image_hdr (hdr);

		data = addr + sizeof (image_header_t);
		len = ntohl (hdr->ih_size);

		if (verify) {
			ulong csum = 0;

			printf ("   Verifying Checksum ... ");
			csum = crc32 (0, (unsigned char *) data, len);
			if (csum != ntohl (hdr->ih_dcrc)) {
				printf ("Bad Data CRC\n");
				SHOW_BOOT_PROGRESS (-12);
				do_reset (cmdtp, flag, argc, argv);
			}
			printf ("OK\n");
		}

		SHOW_BOOT_PROGRESS (11);

		if ((hdr->ih_os != IH_OS_LINUX) ||
		    (hdr->ih_arch != IH_CPU_ARM) ||
		    (hdr->ih_type != IH_TYPE_RAMDISK)) {
			printf ("No Linux ARM Ramdisk Image\n");
			SHOW_BOOT_PROGRESS (-13);
			do_reset (cmdtp, flag, argc, argv);
		}

#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
		/*
		 *we need to copy the ramdisk to SRAM to let Linux boot
		 */
		memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
		data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */

		/*
		 * Now check if we have a multifile image
		 */
	} else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
		ulong tail = ntohl (len_ptr[0]) % 4;
		int i;

		SHOW_BOOT_PROGRESS (13);

		/* skip kernel length and terminator */
		data = (ulong) (&len_ptr[2]);
		/* skip any additional image length fields */
		for (i = 1; len_ptr[i]; ++i)
			data += 4;
		/* add kernel length, and align */
		data += ntohl (len_ptr[0]);
		if (tail) {
			data += 4 - tail;
		}

		len = ntohl (len_ptr[1]);

	} else {
		/*
		 * no initrd image
		 */
		SHOW_BOOT_PROGRESS (14);

		len = data = 0;
	}

	if (data) {
		initrd_start = data;
		initrd_end = initrd_start + len;
	} else {
		initrd_start = 0;
		initrd_end = 0;
	}

	SHOW_BOOT_PROGRESS (15);


#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) || \
    defined (CONFIG_INITRD_TAG) || \
    defined (CONFIG_SERIAL_TAG) || \
    defined (CONFIG_REVISION_TAG) || \
    defined (CONFIG_LCD) || \
    defined (CONFIG_VFD)
	setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
	setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
	setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
	setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
	setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
	if (initrd_start && initrd_end)
		setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
	setup_videolfb_tag ((gd_t *) gd);
#endif
	setup_end_tag (bd);
#endif

	/* we assume that the kernel is in place */
	printf ("\nStarting kernel ...\n\n");

#ifdef CONFIG_USB_DEVICE
	{
		extern void udc_disconnect (void);
                //udc_disconnect (); // cancled by www.100ask.net
	}
#endif

	cleanup_before_linux ();

	theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}

        我将上面的代码大致分为以下几步:

1. 获得命令行参数
2. 将函数指针指向内核的入口地址
3. 检测是否从内存盘初始化内核
    a. 获得内核在内存盘中的地址
    b. 将uImage的头部信息从内存盘中读出
    c. 检测uImage的头部参数ih_magic是否正确

    d. 检测头部的CRC校验值是否正确

    e. 检测镜像的CRC校验值是否正确
    f. 检验uImage的架构是否为arm架构
    g. uImage的镜像类型,以及内存盘的类型

    h. 将内核盘中的内核数据拷贝到内存的加载地址
4. 设置TAG参数
5. 启动Linux前的准备清除工作
6. 启动Linux内核

获得命令行参数:

        这里获得命令行参数来为内核传入启动参数,这里的命令行参数可以使用默认的参数,也可以使用我们在u-boot命令行中设置的参数。当我们定义了宏:CONFIG_CMDLINE_TAG的时候,程序就可以通过getenv来获得命令行参数了。而当我们没有定义宏:CONFIG_CMDLINE_TAG时,程序会使用默认的启动参数。因此我们可以知道宏CONFIG_CMDLINE_TAG是决定启动参数到底选哪个的关键。而这个启动参数还将在下面的TAG参数中使用。获得启动参数的代码为:

#ifdef CONFIG_CMDLINE_TAG
	char *commandline = getenv ("bootargs");
#endif

        上面的 bootargs 就是在u-boot中设置命令行参数时使用的变量。常用的设置启动参数的方式为:set  bootargs  console=ttySAC0 115200 root=/dev/mtdblock3 rootfstype=jffs2 。而上面的"console=ttySAC0 115200 root=/dev/mtdblock3 rootfstype=jffs2"就是我们常用的命令行参数。

将函数指针指向内核的入口地址:

        这里程序将一个函数指针指向内核的入口地址,而通过函数指针的输入参数我们知道要向内核中传递的参数分别为0,机器ID以及TAG参数的首地址。而我们将函数指针指向这个入口地址,而当我们在下面调用这个函数指针的时候就可以直接直接跳转到内核中了,从而实现启动内核的目的。而指针函数的定义以及将函数指针指向内核的入口地址的代码为:

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

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

检测是否从内存盘初始化内核 :
    a. 获得内核在内存盘中的地址
    b. 将uImage的头部信息从内存盘中读出
    c. 检测uImage的头部参数ih_magic是否正确

    d. 检测头部的CRC校验值是否正确

    e. 检测镜像的CRC校验值是否正确
    f. 检验uImage的架构是否为arm架构
    g. uImage的镜像类型,以及内存盘的类型

        其实上面关于uImage头部信息检测的代码我在前面讲解do_bootm函数的时候已经讲解了,这里就不讲了。大家可以看看上面关于这部分内容的介绍。而这部分的代码也与前面讲解do_bootm函数时的代码十分相似,所以这里也就不贴他们的代码了。

设置TAG参数:

        这部分代码同样是这篇文章的重点之一, 我们知道kernel在启动的过程中要读取一些u-boot为其传递的参数来设置kernel的启动参数,而这些参数都是什么,他们以一种什么样的方式传递到kernel中,这些都是我们要讨论的。我们知道u-boot为内核传递参数是有一定的规律性的,而不是想怎么传就怎么传,这样就不符合内核的思想了。而内核的思想是模块化,同时也便于移植,所以他就有一定的规范,而这个规范就是TAG参数,u-boot与内核之间约定用过这种方式来传递参数,即u-boot通过TAG参数的方式将要传输的数据放到指定的地址中,而内核则通过TAG参数的方式将存放在指定地点中的数据读出。这就体现了TAG的参数的重要性了。

        而在u-boot中有很多TAG参数,这里我们选择4个比较重要的参数来向大家说明TAG参数的格式,他们分别为:开始TAG,结束TAG,内存TAG和命令行TAG。我在下图中将它们画出:

        而他们的代码为:

static void setup_start_tag (bd_t *bd)
{
	params = (struct tag *) bd->bi_boot_params;

	params->hdr.tag = ATAG_CORE;
	params->hdr.size = tag_size (tag_core);

	params->u.core.flags = 0;
	params->u.core.pagesize = 0;
	params->u.core.rootdev = 0;

	params = tag_next (params);
}


#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd)
{
	int i;

	for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
		params->hdr.tag = ATAG_MEM;
		params->hdr.size = tag_size (tag_mem32);

		params->u.mem.start = bd->bi_dram[i].start;
		params->u.mem.size = bd->bi_dram[i].size;

		params = tag_next (params);
	}
}
#endif /* CONFIG_SETUP_MEMORY_TAGS */


static void setup_commandline_tag (bd_t *bd, char *commandline)
{
	char *p;

	if (!commandline)
		return;

	/* eat leading white space */
	for (p = commandline; *p == ' '; p++);

	/* skip non-existent command lines so the kernel will still
	 * use its default command line.
	 */
	if (*p == '\0')
		return;

	params->hdr.tag = ATAG_CMDLINE;
	params->hdr.size =
		(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;

	strcpy (params->u.cmdline.cmdline, p);

	params = tag_next (params);
}

static void setup_end_tag (bd_t *bd)
{
	params->hdr.tag = ATAG_NONE;
	params->hdr.size = 0;
}

        其中开始TAG和结束TAG分别标识着TAG参数的开始和结束位置,而内存TAG是向内核传递内存的首地址和内核的大小,最后命令行TAG就是我们在前面获得的命令行信息。而这个信息在内核的启动中非常有用。


启动Linux前的准备清除工作:

        启动Linux前的准备工作其实主要是关中断以及关I/D cache。他的代码为:

int cleanup_before_linux (void)
{
	/*
	 * this function is called just before we call linux
	 * it prepares the processor for linux
	 *
	 * we turn off caches etc ...
	 */

	unsigned long i;

	disable_interrupts ();

	/* turn off I/D-cache */
	asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i));
	i &= ~(C1_DC | C1_IC);
	asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i));

	/* flush I/D-cache */
	i = 0;
	asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i));

	return (0);
}

启动Linux内核:

        而完成上面的所有操作之后我们就可以启动Linux了。而启动的代码也很简单就是直接调用函数指针就可以了。他的代码为:

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

        好了,讲到这里关于u-boot的知识就都讲完了,希望对您有所帮助。

参考文章:

zImage和uImage的区别

kernel编译生成Image zImage uImage的区别

uboot 命令分析(一) — bootm

u-boot源代码

[uboot] uboot启动kernel篇(三)——uboot解析uImage的kernel信息

ARM uboot Legacy uImage 和 fit img (Flattened uImage Tree)原理介绍

 

 

 

 

 

没有更多推荐了,返回首页