U-BOOT 在启动内核时,会向内核传递一些参数.BootLoader 可以通过两种方法传递参数给内核,一种是旧的参数结构方式(parameter_struct),主要是 2.6 之前的内核使用的方式。另外一种就是现在的 2.6内核在用的参数链表 (tagged list) 方式。这些参数主要包括,系统的根设备标志,页面大小,内存的起始地址和大小,RAMDISK的起始地址和大小,压缩的RAMDISK根文件系统的起始地址和大小,当前内核命令参数等而这些参数是通过 struct tag来传递的。U-boot 把要传递给 kernel 的东西保存在 struct tag 数据结构中,启动 kernel 时,把这个结构体的物理地址传给kernel;Linux kernel 通过这个地址分析出u-boot传递的参数。大家都知道U-Boot启动的时候会将启动参数的地址放入R2中,然后再启动内核。
首先看两个重要的数据结构:
第一个是global_data,定义在include/asm-arm/global_data.h文件中:
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
#if 0
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
unsigned long ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table */
} gd_t;
在同一个文件中有如下定义:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
在需要使用gd指针的时候,只需要加入DECLARE_GLOBAL_DATA_PTR这句话就可以了。可以知道,gd指针始终是放在r8中的。
其中的第一个变量,bd_t结构体,定义于include/asm-arm/u-boot.h中:
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
unsigned long bi_ip_addr; /* IP Address */
unsigned char bi_enetaddr[6]; /* Ethernet adress */
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */
struct /* RAM configuration */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
bd_t中的变量bi_boot_params,表示传递给内核的参数的位置。
然后看看gd和bd的初始化,在lib_arm/board.c中:
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
说明这两个结构体在内存中的位置是在uboot的代码在往下的地址处,所以进行操作的时候不要覆盖了这个位置!
在board/smdk2410/smdk2410.c中,有如下初始化:
gd->bd->bi_boot_params = 0x30000100; 说明参数位置在0x30000100。
现在,具体看看uboot是如何(按什么格式)把参数放入内存中。
内核参数链表的格式和说明可以从内核源代码目录树中的 include/asm-arm/setup.h中找到,参数链表必须以ATAG_CORE 开始,以ATAG_NONE结束。这里的 ATAG_CORE,ATAG_NONE是各个参数的标记,本身是一个32位值,例如:ATAG_CORE=0x54410001。
其它的参数标记还包括: ATAG_MEM32 , ATAG_INITRD , ATAG_RAMDISK ,ATAG_COMDLINE 等。每个参数标记就代表一个参数结构体,由各个参数结构体构成了参数链表。参数结构体的定义如下:
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
struct tag_acorn acorn;
struct tag_memclk memclk;
} u;
};
参数结构体包括两个部分,一个是 tag_header结构体,一个是u联合体。
tag_header结构体的定义如下:
struct tag_header {
u32 size;
u32 tag;
};
其中 size:表示整个tag 结构体的大小(用字的个数来表示,而不是字节的个数),等于tag_header的大小加上u联合体的大小,例如,参数结构体 ATAG_CORE 的 size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通过函数 tag_size(struct * tag_xxx)来获得每个参数结构体的size。
其中 tag:表示整个tag 结构体的标记,如:ATAG_CORE等。
联合体 u 包括了所有可选择的内核参数类型,包括:tag_core, tag_mem32,tag_ramdisk等。参数结构体之间的遍历是通过函数 tag_next(struct * tag)来实现的。本系统参数链表包括的结构体有:ATAG_CORE,ATAG_MEM,ATAG_RAMDISK,ATAG_INITRD32,ATAG_CMDLINE,ATAG_END。在整个参数链表中除了参数结构体 ATAG_CORE 和ATAG_END 的位置固定以外,其他参数结构体的顺序是任意的。本 BootLoader所传递的参数链表如下:第一个内核参数结构体,标记为ATAG_CORE,参数类型为 tag_core。每个参数类型的定义请参考源代码文件。
我们知道u-boot传递给内核的参数有很多个,如系统的根设备标志,页面大小,内存的起始地址和大小,RAMDISK的起始地址和大小,压缩的RAMDISK根文件系统的起始地址和大小等,而每个参数我们都是单独的采用一个struct tag来标识的,之前提到的参数标记如ATAG_MEM32,ATAG_INTRD等就是用来标识该tag结构是用来存放的哪种类型的参数。由于不同类型的参数传递的信息内容也不尽相同,为了综合不同参数的tag结构,所以在struct tag结构中定义了一个联合体union,根据不同的参数标记符来选择联合体中不同的结构体来存储参数的内容,如参数标记若为ATAG_MEM32,则联合体中采用struct tag_mem32来存储内存参数的内容。
然而内核是如何从gd->bd->bi_boot_params指定的地址上知道参数从哪里开始以及到哪里结束呢?
所以我们在构建各种参数tag时,在开始时先要构建一个参数标记为ATAG_CORE的tag结构标示从这个tag结构开始接下来就是参数
现来结合代码分析在u-boot中是如何来构建这多个参数的tag结构:
/common/cmd_bootm.c 文件中,bootm 命令对应的do_bootm函数,当分析 uImage 中信息发现 OS 是 Linux 时 ,调用 ./lib_arm/bootm.c 文件中的do_bootm_linux 函数来启动 Linux kernel 。
#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); //通过bd结构体中参数在内存中的存放地址gd->bd->bi_boot_params来构建初始化的tag结构,表明参数结构的开始
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms); //构建串口参数的tag结构
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd); //构建内存参数的tag结构
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline); //构建命令行参数的tag结构
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end); //构建ramdisk参数的tag结构
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd); //最后是构建参数tag结构结束的tag结构,标示参数已经结束,参数标记为ATAG_NONE
#endif
注意上面参数的tag结构的构建是有宏的约束的,再来看看具体是怎样构建每个tag结构的:
#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)
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;//将指定的内存中存放参数列表的地址强制转化为struct tag的结构,这样便于内核存取各个参数
params->hdr.tag = ATAG_CORE; //标示这个tag结构是用来标示参数结构的开始
params->hdr.size = tag_size (tag_core); //存放整个tag结构的大小
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
其中用到了一个重要的指针:params,这是一个指向struct tag的指针,在文件的开始处声明,可以被这个文件中的所有函数访问:static struct tag *params;
tag和tag_header和内核中的结构一模一样。tag_header中的tag字段表示的是这个tag的类型,在内核和Bootloader中通过一些固定的整形常量来表示:
#define ATAG_CORE 0x54410001
#define ATAG_NONE 0x00000000
#define ATAG_CORE 0x54410001
#define ATAG_MEM 0x54410002
#define ATAG_VIDEOTEXT 0x54410003
#define ATAG_RAMDISK 0x54410004
#define ATAG_INITRD 0x54410005
#define ATAG_INITRD2 0x54420005
#define ATAG_SERIAL 0x54410006
#define ATAG_REVISION 0x54410007
#define ATAG_VIDEOLFB 0x54410008
#define ATAG_CMDLINE 0x54410009
#define ATAG_ACORN 0x41000101
#define ATAG_MEMCLK 0x41000402
上面是初始化tag链表(在SDRAM里),最后一句是作为链表的最关键部分,它的定义是:
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size)) 作用是指向下一个tag结构体。一般在每个参数的tag结构体的最后都要调用这个宏,内核在遇到这个宏就可以直接跳转到下一个参数tag结构体的地址上来存取。
再来看看其他参数种类的tag结构的构建
#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);
}
}
其中 defined (CONFIG_SETUP_MEMORY_TAGS) 和 defined (CONFIG_CMDLINE_TAG) 是必不可少的。前者是标记内存的信息,而后者是设置命令行标记(比如“root=/dev/mtdblock2 init=/linuxrc console=ttySAC0”)
到最后可以看到调用:theKernel (0, machid, bd->bi_boot_params);
当然,有很多的宏来选择是否传递相应的tag到linux kenel.实际是这些所以针对于 bd->bi_boot_params 这个变量.这个变量是个整形变量,代表存放所有tag的buffer的地址.
例如,在 smdk2410.c 中的 board_init() 函数中,对于这个变量进行了如下赋值:
gd->bd->bi_boot_params = 0x30000100;
0x30000100 这个值可以随意指定, 但是要保证和内核中相应的mach_type 一致.以smdk2410为例:
在内核中始终这个值的地方是: arch\arm\mach-s3c2410\mach-smdk2410.c的最后
MACHINE_START(SMDK2410, "SMDK2410")
.phys_ram = S3C2410_SDRAM_PA,
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io,
.init_irq = smdk2410_init_irq,
.timer = &s3c24xx_timer,
MACHINE_END
红色部分的值, 必须等于0x30000100, 否者将会出现无法启动的问题.
内核启动后,会读取0x300000100位置的值, 当然,内核会把这个地址转换成逻辑地址在操作. 因为内核跑起来后,MMU已经工作, 必须要把0x300000100这个物理地址转成逻辑地址然后在操作.对于u- boot传给内核的参数中(tag), 内核比较关系memory的信息,比如memory地址的起始,大小等.如果没有得到,那么内核无法启 动,内核会进入BUG()函数,然后死在那里.
而memory的信息是由 CONFIG_SETUP_MEMORY_TAGS 宏决定的. 因此当这个宏没有被定义时,内核跑不起来. 初始化meminfo时会失败. 现象就是:
Starting Kernel ...
死掉.
一般需要定义:
#define CONFIG_SETUP_MEMORY_TAGS
#define CONFIG_CMDLINE_TAG
// 传给 Kernel 的参数= (struct tag *) 型的 bd->bi_boot_params [1]
===================================================================
UBOOT引导Linux内核及向内核传递参数的方式
一直以来没有想过有什么好的办法通过寄存器向内核传递参数,直到今天读UBOOT的实现方式。
在UBOOT中,引导内核最常用的方法是bootm命令,bootm命令可以引导“UBOOT格式”的内核。先花点时间了解一下什么是“UBOOT格式”的内核吧:用UBOOT自带的mkimage命令生成的内核称为"UBOOT"格式的内核。以下面这条命令为例:
mkimage -n "Kernel 2.4.18" -A arm -O linux -T kernel -C none -a 30007fc0 -e 30008000 -d 4020.bin vmlinux-2.4.18.img
其中与内核引导最密切的是-e 30008000,也就是内核的入口地址。其它参数可以参考帮助信息。其它UBOOT格式的内核与原来相比,只是进行(可选)了压缩,并在前面加了一个0x40大小的头。这个头里放了内核的位置(0x30007fc0)和入口地址(0x30008000)和其它信息。
bootm命令执行时,先对头部信息等进行校验,然后把头信息放到一个结构里面。最后根据内核类型调用相应的启动函数。对于Linux而言就是do_bootm_linux,在启动函数里面,有这么一个操作:theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);,这是最关键的一个操作,将内核的入口地址0x30008000赋给了theKernel,在启动函数的最后,使用theKernel (0, bd->bi_arch_number, bd->bi_boot_params);启动内核。
根据传参规范,三个变量分别用r0,r1,r2传给内核,这样就巧妙地利用了函数指针进行了参数传递,实在是精妙!
上面讲完了内核的引导及传参,需要引起注意的就是在使用mkimage命令生成内核时,-e后面的地址要比-a后面的地址偏移0x40,原因很简单,就不在细说了。 [2]
参考:
[1] http://blog.csdn.net/uyiwfn/article/details/7172352
[2] http://www.armfans.net/thread-109-1-1.html
说明:本文所使用的U-Boot的版本是1.1.6,平台是S3C2440。
目录
一、简介
1.1标记列表
二、设置标记存放的地址
2.1相关的结构体定义
2.2标记存放地址的设定
三、标记的设置
3.1设置标记ATAG_CORE
3.2设置内存标记ATAG_MEM
3.3设置命令行标记ATAG_CMDLINE
3.4设置ATAG_NONE
一、简介
U-Boot与Linux内核的交互是单向的,U-Boot将各类参数传递给讷河。由于他们不能同时运行,传递办法只能有一个个:U-Boot将参数放在某个约定的地方之后,在启动内核,内核启动后从这个地方获得参数。
1.1标记列表
除了约定好参数存放的地方外,还要规定参数的结构。Linux2.4.x以后的内核都以标记列表(tagged list)的形式来传递参数。标记就是一种数据结构;标记列表就是挨着存放的多个标记。标记列表以标记ATAG_CORE开始,以ATAGE_NONE结束。
标记的数据结构为tag,它是偶一个tag_header结构和一个联合体(union)组成。tag_header结构体表示标记的类型及长度,比如是表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合体,比如表示内存=时使用tag_men32,表示命令行时使用tag_cmdline。其定定义在include/asm-arm/setup.c文件中。
/*
* The new way of passing information: a list of tagged entries
*/
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE 0x00000000
struct tag_header {
u32 size;
u32 tag;
};
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001
struct tag_core {
u32 flags; /* bit 0 = read-only */
u32 pagesize;
u32 rootdev;
};
/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM 0x54410002
struct tag_mem32 {
u32 size;
u32 start; /* physical start address */
};
/* VGA text type displays */
#define ATAG_VIDEOTEXT 0x54410003
struct tag_videotext {
u8 x;
u8 y;
u16 video_page;
u8 video_mode;
u8 video_cols;
u16 video_ega_bx;
u8 video_lines;
u8 video_isvga;
u16 video_points;
};
/* describes how the ramdisk will be used in kernel */
#define ATAG_RAMDISK 0x54410004
struct tag_ramdisk {
u32 flags; /* bit 0 = load, bit 1 = prompt */
u32 size; /* decompressed ramdisk size in _kilo_ bytes */
u32 start; /* starting block of floppy-based RAM disk image */
};
/* describes where the compressed ramdisk image lives (virtual address) */
/*
* this one accidentally used virtual addresses - as such,
* its depreciated.
*/
#define ATAG_INITRD 0x54410005
/* describes where the compressed ramdisk image lives (physical address) */
#define ATAG_INITRD2 0x54420005
struct tag_initrd {
u32 start; /* physical start address */
u32 size; /* size of compressed ramdisk image in bytes */
};
/* board serial number. "64 bits should be enough for everybody" */
#define ATAG_SERIAL 0x54410006
struct tag_serialnr {
u32 low;
u32 high;
};
/* board revision */
#define ATAG_REVISION 0x54410007
struct tag_revision {
u32 rev;
};
/* initial values for vesafb-type framebuffers. see struct screen_info
* in include/linux/tty.h
*/
#define ATAG_VIDEOLFB 0x54410008
struct tag_videolfb {
u16 lfb_width;
u16 lfb_height;
u16 lfb_depth;
u16 lfb_linelength;
u32 lfb_base;
u32 lfb_size;
u8 red_size;
u8 red_pos;
u8 green_size;
u8 green_pos;
u8 blue_size;
u8 blue_pos;
u8 rsvd_size;
u8 rsvd_pos;
};
/* command line: \0 terminated string */
#define ATAG_CMDLINE 0x54410009
struct tag_cmdline {
char cmdline[1]; /* this is the minimum size */
};
/* acorn RiscPC specific information */
#define ATAG_ACORN 0x41000101
struct tag_acorn {
u32 memc_control_reg;
u32 vram_pages;
u8 sounddefault;
u8 adfsdrives;
};
/* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */
#define ATAG_MEMCLK 0x41000402
struct tag_memclk {
u32 fmemclk;
};
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
/*
* Acorn specific
*/
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
#define tag_next(t)<span style="white-space:pre"> </span>((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type)<span style="white-space:pre"> </span>((sizeof(struct tag_header) + sizeof(struct type))>> 2) //???
二、设置标记存放的地址
2.1相关的结构体定义
结构体bd中保存了标记存放的地址。bd结构体是gd结构体的一项,我们先看gd结构体,其定义在include/asm-arm/global_data.h文件中:
typedef struct global_data {
bd_t *bd;//开发板相关参数 ,结构体变量,参考u-boot.h
unsigned long flags;//指示标志,如设备已经初始化标志等
unsigned long baudrate;//串行口通讯速率
unsigned long have_console;
/* serial_init() was called 如果执行了该函数,则设置为1 */
unsigned long reloc_off;
/*
*Relocation Offset 重定位偏移,就是实际定向的位置与编译连接时指定的位置之差,一般为0
*/
unsigned long env_addr; /* 环境参数地址*/
unsigned long env_valid; /* 环境参数CRC检验有效标志*/
unsigned long fb_base; /*帧缓冲区基地址*/
#ifdef CONFIG_VFD
unsigned char vfd_type; /* 显示类型*/
#endif
#if 0
unsigned long cpu_clk; /*cpu时钟*/
unsigned long bus_clk; //总线时钟
unsigned long ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table 跳转表,用来登记"函数调用地址"*/
} gd_t;
接来下我们来看一下bd结构体,这个结构体定义在include/asm-arm/u-boot.h文件中:
typedef struct bd_info {
int bi_baudrate; /* 串口波特率*/
unsigned long bi_ip_addr; /* IP 地址*/
unsigned char bi_enetaddr[6]; /* MAC地址*/
struct environment_s *bi_env;
ulong bi_arch_number; /* 板子的id*/
ulong bi_boot_params; /* 启动参数*/
struct /* RAM 配置*/
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS];
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
2.2标记存放地址的设定
在board/smdk2410/smdk2410.c的board_init 函数设置了bi_boot_params 参数:
int board_init (void)
{
S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();//获取时钟和电源配置寄存器的第一个寄存器的地址,寄存器的地上是连续的
S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();//获取GPIO配置寄存器的第一个寄存器的地址
/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFF;
/* configure MPLL */
clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);
/* some delay between MPLL and UPLL */
delay (4000);
/* configure UPLL */
clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);
/* some delay between MPLL and UPLL */
delay (8000);
/* set up the I/O ports */
gpio->GPACON = 0x007FFFFF;
gpio->GPBCON = 0x00044555;
gpio->GPBUP = 0x000007FF;
gpio->GPCCON = 0xAAAAAAAA;
gpio->GPCUP = 0x0000FFFF;
gpio->GPDCON = 0xAAAAAAAA;
gpio->GPDUP = 0x0000FFFF;
gpio->GPECON = 0xAAAAAAAA;
gpio->GPEUP = 0x0000FFFF;
gpio->GPFCON = 0x000055AA;
gpio->GPFUP = 0x000000FF;
gpio->GPGCON = 0xFF95FFBA;
gpio->GPGUP = 0x0000FFFF;
gpio->GPHCON = 0x002AFAAA;
gpio->GPHUP = 0x000007FF;
/* arch number of SMDK2410-Board */
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
/* adress of boot parameters */
gd->bd->bi_boot_params = 0x30000100;
icache_enable(); //调用cpu/arm920t/cpu.c中的函数
dcache_enable();
return 0;
}
三、标记的设置
U-Boot通过bootm命令引导Linux内核,bootm命令对吼调用do_bootm_linux函数来引导内核。在do_bootm_linux函数就设置了标记,该函数的定义在lib_arm/armlinux.c中:
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);
设置kernal加载地址
/*
* Check if there is an initrd image
*/
用户自定义了initrd之后需要加载进来,整个过程需要进行头部以及整个数据内部校,类似于内核的加载校验,这里省略了。
initial RAM disk Linux初始 RAM磁盘(initrd)是在系统引导过程中挂载的一个临时根文件系统,用来支持两阶段的引导过程。initrd文件中包含了各种可执行程序和驱动程序,它们可以用来挂载实际的根文件系统,然后再将这个 initrd RAM 磁盘卸载,并释放内存。在很多嵌入式Linux 系统中,initrd 就是最终的根文件系统。
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 */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (addr, sizeof (image_header_t),
(char *) &header);
} else
#endif
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);
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
data = CFG_LOAD_ADDR;
}
#endif
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;
}
#ifdef DEBUG
if (!data) {
printf ("No initrd\n");
}
#endif
if (data) {
initrd_start = data;
initrd_end = initrd_start + len;
} else {
initrd_start = 0;
initrd_end = 0;
}
SHOW_BOOT_PROGRESS (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
#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);设置各种tag,用于传递参数给Linux
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (?ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (?ms);
#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 ();
}
#endif
cleanup_before_linux ();启动之前先做一些清理工作cpu/arm920t/cpu.c
调用内核需要传递的参数如下:
R0:必须为0
R1:机器类型ID,本机为ARM(bd->bi_arch_number)
R2:启动参数列表在内存中的位置(bd->bi_boot_params)
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}
3.1设置标记ATAG_CORE
标记列表以标记ATAG_CORE开始
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);//指向当前标记的末尾
}
3.2设置内存标记ATAG_MEM
在board/smdk2410/smdk2410.c的dram_init函数设置了bd的bi_dram结构体:
int dram_init (void
{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
return 0;
}
下面是这边内存标记的结构体:
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);
}
}
3.3设置命令行标记ATAG_CMDLINE
命令行就是一个字符串,用来控制内核的一些行为。比如“root=/dev/mtdblock2 init=/linuxrc console=ttySAC0 ”表示根文件系统在MTD2分区上系统启动后执行的第一个程序为/linuxrc,控制台是ttySAC0 。
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);
}
3.4设置ATAG_NONE
标记列表以标记ATAG_NONE介绍。
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;