学习http://blog.youkuaiyun.com/boarmy/article/details/8652756博文并根据X4412-UBOOT源码整理
U-boot会给LinuxKernel传递很多参数,如:串口,RAM,videofb、MAC地址等。而Linuxkernel也会读取和处理这些参数。两者之间通过structtag来传递参数。U-boot把要传递给kernel的东西保存在structtag数据结构中,启动kernel时,把这个结构体的物理地址传给kernel;Linuxkernel通过这个地址,用parse_tags分析出传递过来的参数。
在介绍该之前,我们需要看一看几个数据结构,这些是u-boot中几个重要的数据结构:
(1)gd_t结构体
U-Boot使用了一个结构体gd_t来存储全局数据区的数据,这个结构体在U-Boot的arch/arm/include/asm/global_data.h中定义如下:
typedef struct global_data{
bd_t *bd; /* 与板子相关的结构*/
unsignedlong flags;
unsignedlong baudrate;
unsignedlong have_console; /* serial_init() was called */
unsignedlong env_addr; /* Address of Environment struct */
unsignedlong env_valid; /* Checksum of Environment valid? */
unsignedlong fb_base; /* base address of frame buffer */
#ifdefCONFIG_VFD //未定义
unsignedchar vfd_type; /* display type */
#endif
#ifdefCONFIG_FSL_ESDHC //未定义
unsignedlong sdhc_clk;
#endif
#ifdefCONFIG_AT91FAMILY //未定义
/*"static data" needed by at91's clock.c */
unsignedlong cpu_clk_rate_hz;
unsignedlong main_clk_rate_hz;
unsignedlong mck_rate_hz;
unsignedlong plla_rate_hz;
unsignedlong pllb_rate_hz;
unsignedlong at91_pllb_usb_init;
#endif
#ifdefCONFIG_ARM //未定义
/*"static data" needed by most of timer.c on ARM platforms */
unsignedlong timer_rate_hz;
unsignedlong tbl;
unsignedlong tbu;
unsignedlong long timer_reset_value;
unsignedlong lastinc;
#endif
unsignedlong relocaddr; /* Start address of U-Boot in RAM */
phys_size_t ram_size; /*RAM size */
unsignedlong mon_len; /* monitor len */
unsignedlong irq_sp; /* irq stack pointer */
unsignedlong start_addr_sp; /* start_addr_stackpointer */
unsignedlong reloc_off;
#if!(defined(CONFIG_SYS_NO_ICACHE) &&defined(CONFIG_SYS_NO_DCACHE))
unsignedlong tlb_addr;
#endif
void **jt; /*jump table */
char env_buf[32]; /*buffer for getenv() before reloc. */
}gd_t;
/*
*Global Data Flags
*/
#define GD_FLG_RELOC 0x00001 /*Code was relocated to RAM */
#define GD_FLG_DEVINIT 0x00002 /*Devices have been initialized */
#define GD_FLG_SILENT 0x00004 /*Silent mode */
#define GD_FLG_POSTFAIL 0x00008 /*Critical POST test failed */
#define GD_FLG_POSTSTOP 0x00010 /*POST seqeunce aborted */
#define GD_FLG_LOGINIT 0x00020 /*Log Buffer has been initialized */
#defineGD_FLG_DISABLE_CONSOLE 0x00040 /* Disable console (in & out) */
#defineGD_FLG_ENV_READY 0x00080 /* Environment imported into hash table */
#defineDECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
在global_data.h中U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址:
#defineDECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
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)
(2)bd_t保存与板子相关的配置参数
bd_t在U-Boot的arch/arm/include/asm/u-boot.h中定义如下:
typedefstruct bd_info {
int bi_baudrate; /* serial console baudrate*/
unsigned long bi_ip_addr; /* IP Address */
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */
struct /* RAM configuration */
{
ulong start;
ulongsize;
} bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
其中CONFIG_NR_DRAM_BANKS在板子配置文件include/configs/x4412.h中定义,X4412开发板中使用1G内存。CONFIG_NR_DRAM_BANKS定义为4.
U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表。
(3)启动参数的数据结构
向内核传递启动参数可保存在两种数据结构中,param_struct和tag,前者是2.4内核用的,后者是2.6以后的内核更期望用的但是,到目前为止,2.6的内核也可以兼容前一种结构,内核参数通过一个静态的param_struct或tag链表在启动的时候传递到内核。需要注意的是,这两个数据结构在uboot中和linux中分别有定义,这个定义必须一致才能正常传递参数如果实际使用中不一致的话就不能正常传递,可以自行修改两种数据结构具体定义如下(这里说的是uboot源码中的arch/arm/include/asm/setup.h定义,linux3版本不采用这个结构体):
关于param_struct新版U-BOOT已经不推荐使用这里不再作介绍,下面主要分析tag
对于tag来说,在实际使用中是一个structtag组成的列表,在tag->tag_header中,一项是u32tag(重名,注意类型)其值用宏ATAG_CORE,ATAG_MEM,ATAG_CMDLINE,ATAG_NONE等等来表示,此时下面union就会使用与之相关的数据结构同时,规定tag列表中第一项必须是ATAG_CORE,最后一项必须是ATAG_NONE,比如在linux代码中,找到启动参数之后首先看tag列表中的第一项的tag->hdr.tag是否为ATAG_CORE,如果不是,就会认为启动参数不是tag结构而是param_struct结构,然后调用函数来转换.在tag->tag_header中,另一项是u32size,表示tag的大小,tag组成列表的方式就是指针+size
tag数据结构在arch/arm/include/asm/setup.h(U-Boot的在arch/arm/include/asm/setup.h定义,完全一样)中定义如下:
structtag {
struct tag_header hdr;
union {
structtag_core core;
struct tag_mem32 mem;
structtag_videotext videotext;
struct tag_ramdisk ramdisk;
structtag_initrd initrd;
struct tag_serialnr serialnr;
structtag_revision revision;
struct tag_videolfb videolfb;
structtag_cmdline cmdline;
/*
* Acorn specific
*/
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
}u;
};
其中tag_header为tag头,表明tag_xxx的类型和大小,之所以要标识tag_xxx的类型是因为不同的tag需要不同的处理函数。
内核tag_header的结构(arch/arm/include/asm/setup.h)为
structtag_header {
__u32 size; /*表示tag数据结构的联合u实质存放的数据的大小*/
__u32 tag; /*表示标记的类型*/
};
U-Boot的在arch/arm/include/asm/setup.h定义
structtag_header {
u32 size;
u32tag;
};
size表示tag的结构大小,tag为表示tag类型的常量。这个静态的链表必须以tag_header.tag= ATAG_CORE开始,并以tag_header.tag=ATAG_NONE结束。由于不同的tag所使用的格式可能不尽相同,所以内核又定义了一个结构tagtable来把tag和相应的操作函数关联起来
(arch/arm/include/asm/setup.h)
structtagtable {
__u32 tag;
int (*parse)(const structtag*);
};
其中tag为标识入ATAG_NONE,ATAG_CORE等。parse为处理函数。Linux内核将tagtable也组成了一个静态的链表放入.taglist.init节中,这是通过__tagtable宏来实现的
#define__tag __used __attribute__((__section__(".taglist.init")))
#define__tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag= { tag, fn }
这个tagtable列表 是怎么形成的?
如(内核arch/arm/kernel/setup.c)
staticint __init parse_tag_mem32(const struct tag *tag)
{
returnarm_add_memory(tag->u.mem.start, tag->u.mem.size);
}
__tagtable(ATAG_MEM, parse_tag_mem32);
__tagtable(ATAG_SERIAL, parse_tag_serialnr);
static int__init parse_tag_revision(const struct tag *tag)
{
system_rev = tag->u.revision.rev;
return 0;
}
__tagtable(ATAG_REVISION, parse_tag_revision);
static int__init parse_tag_cmdline(const struct tag *tag)
{
strlcpy(default_command_line, tag->u.cmdline.cmdline,COMMAND_LINE_SIZE);
return 0;
}
__tagtable(ATAG_CMDLINE,parse_tag_cmdline);
根据前面相关宏定义,__tagtable(ATAG_CMDLINE,parse_tag_cmdline)展开后为
staticstruct tagtable __tagtable_parse_tag_cmdline __used__attribute__((__section__(".taglist.init"))) = {ATAG_CMDLINE, parse_tag_cmdline}
__tagtable将ATAG_CMDLINE和parse_tag_cmdline挂钩,
再参看内核中arch/arm/kernel/vmlinux.lds.S文件
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end= .;
tagtable 列表编译连接后被存放在.taglist.init中。
现在再来看一下U-boot给LinuxKernel传递启动参数的传递过程
启动参数是包装在structtag数据结构里的,在linuxkernel启动的时候,bootloader把这个数据结构拷贝到某个地址,在改动PC跳向内核接口的同时,通过通用寄存器R2来传递这个地址的值,在bootm执行的流程中,会调用do_bootm_linux()在执行Linux内核,内核的起始地址如下:
void(*theKernel)(int zero, int arch, uint params);
image_header_t *hdr= &header;
theKernel = (void (*)(int, int,uint))ntohl(hdr->ih_ep);
header是uImage的头部,通过头部,得到内核映像起始的执行地址,标识为theKernel。从中也可以看到,内核接受三个参数,第一个为0,第二个为系统的ID号,第三个是传入内核的参数。
在do_bootm_linux()的最后,会跳到内核去执行:
theKernel (0, bd->bi_arch_number,bd->bi_boot_params);
thekernel其实不是个函数,而是指向内核入口地址的指针,把它强行转化为带三个参数的函数指针,会把三个参数保存到通用寄存器中,实现了向kernel传递信息的功能,在这个例子里,把R0赋值为0,R1赋值为机器号bd->bi_arch_number,R2赋值为启动参数数据结构的首地址bd->bi_boot_params。最后两个参数在board/samsung/x4212/x4212.c的board_init()中被初始化。
因此,要向内核传递参数很简单,只要把启动参数封装在linux预定好的数据结构里,拷贝到某个地址(一般约定俗成是内存首地址+100dex,后面会见到)
现在我们来具体分析U-boot向内核传递参数的具体实现过程
a、在arch/arm/include/asm/global_data.h中声名一个gd全局指针变量宏定义,并指定存放在r8寄存器中,在后面要用到gd全局指针变量时,只须要在文件开头引用这个宏就可以了。
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm("r8")
b、在start_armboot(arch/arm/lib/board.c)主函数中计算全局数据结构的地址并赋值给指针gd,并对structtag数据结构里参数赋值。start_armboot分为board_init_f和board_init_r两部分(两个函数执行)
下面是start_armboot函数部分代码
DECLARE_GLOBAL_DATA_PTR; //gd指针引用声名
先在board_init_f函数中:
1、为gd数据结构分配地址,并清零
/*Pointer is writable since we allocated a register for it */
gd =(gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);//分配地址0x42e00000在x4412.h中定义
/*compiler optimization barrier needed for GCC >= 3.4 */
__asm____volatile__("": : :"memory");
memset((void*)gd, 0, sizeof (gd_t)); //清零
gd->mon_len= _bss_end_ofs; //长度
2、执行init_fnc_ptr函数指针数组中的各个初始化函数,函数如下(board_early_init_f,timer_init, env_init,init_baudrateserial_init,console_init_f,display_banner,dram_init)
代码:
for(init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if((*init_fnc_ptr)() != 0) {
hang ();
}
}
3、A、分配SDRAM高64KB为TLB,用于U-BOOT
B、分配SDRAM下一单元为U-BOOT代码段,数据段,BSS段
(这里插一句,原来BSS段是用来存放未初始化的全局变量与静态变量)
C、接着开辟malloc空间,存bd, gd , 3个字大小的异常堆空间
4、将relorate的地址值赋给gd结构体相应变量(用于返回start.S)
board_init_f函数详细分析见“UBOOT之源码分析(X4412)——板级初始化”
此时relocate_code不再是从flash中拷贝u-boot代码到内存了,而是从内存中TEXT_BASE处将代码拷贝到board_init_f中计算的addr位置。
然后在board_init_r函数中:
1、对gd, bd 数据结构赋值初始化
2、各种外设初始化:
初始化NORFLASH, NANDFLASH, 初始化ONENANDFLASH
初始化环境变量初始化PCI设置IP地址初始化各类外设:IIC、LCD、键盘、USB 初始化控制台 建立IRQ中断堆栈 初始化以太网
初始化跳转表(定义了U-Boot中基本的常用函数库)。。这不算外设
3、一个死循环执行 main_loop()函数
详细分析见“UBOOT之源码分析(X4412)——板级第二阶段初始化”
下面分析后面用到的硬件初始化函数board_init、dram_init_banksize。这两个函数都在board/samsung/x4212/x4212.c中实现
首先看board_init函数,以下是部分实现
DECLARE_GLOBAL_DATA_PTR;
#ifdefCONFIG_SMDKC220
gd->bd->bi_arch_number= MACH_TYPE_C220;
#else
if(((PRO_ID& 0x300) >> 8) == 2)
gd->bd->bi_arch_number= MACH_TYPE_C210;
else
gd->bd->bi_arch_number= MACH_TYPE_V310;
#endif
/*adress of boot parameters */
gd->bd->bi_boot_params= (PHYS_SDRAM_1+0x100);//一般约定俗成是内存首地址+100dex
这里PHYS_SDRAM_1在include/configs/x4412.h中定义:
282:#definePHYS_SDRAM_1 CONFIG_SYS_SDRAM_BASE /* SDRAM Bank #1 */
而CONFIG_SYS_SDRAM_BASE在include/configs/x4412.h中定义为0x40000000:
138:#defineCONFIG_SYS_SDRAM_BASE 0x40000000
可以看到,theKernel最后两个参数在这里被初始化,uboot传给内核的参数表存被放在内存中起始偏移0x100的位置,这里只是指定了“指针”的位置,但还没初始化其中的值,后面传递到内核的参数列表的构建才初始化其中的值,这是在do_bootm_linux()中跳到内核前去完成的。值得注意的是,内核的默认运行地址的0x40008000,前面就是留给参数用的。所以一般不要将内核下载到该地址之前,以免冲掉了传给内核的参数。这里MACH_TYPE_C220在include/configs/x4412.h中定义,值为3765,这个值和内核(arch/arm/tools/mach-types文件中)对应的值要相同。
machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number
x4412 MACH_X4412 X4412 3765
include/generated/mach-types.h定义:#defineMACH_TYPE_X4412 3765
而dram_init_banksize函数是对structtag数据结构里内存参数赋值,后面会用到。
void dram_init_banksize(void)
{
nr_dram_banks= CONFIG_NR_DRAM_BANKS;
gd->bd->bi_dram[0].start= PHYS_SDRAM_1;
gd->bd->bi_dram[0].size= PHYS_SDRAM_1_SIZE;
gd->bd->bi_dram[1].start= PHYS_SDRAM_2;
gd->bd->bi_dram[1].size= PHYS_SDRAM_2_SIZE;
gd->bd->bi_dram[2].start= PHYS_SDRAM_3;
gd->bd->bi_dram[2].size= PHYS_SDRAM_3_SIZE;
gd->bd->bi_dram[3].start= PHYS_SDRAM_4;
gd->bd->bi_dram[3].size= PHYS_SDRAM_4_SIZE;
#ifdefUSE_2G_DRAM
gd->bd->bi_dram[4].start= PHYS_SDRAM_5;
gd->bd->bi_dram[4].size= PHYS_SDRAM_5_SIZE;
gd->bd->bi_dram[5].start= PHYS_SDRAM_6;
gd->bd->bi_dram[5].size= PHYS_SDRAM_6_SIZE;
gd->bd->bi_dram[6].start= PHYS_SDRAM_7;
gd->bd->bi_dram[6].size= PHYS_SDRAM_7_SIZE;
gd->bd->bi_dram[7].start= PHYS_SDRAM_8;
gd->bd->bi_dram[7].size= PHYS_SDRAM_8_SIZE;
#endif
#ifdefCONFIG_TRUSTZONE
gd->bd->bi_dram[nr_dram_banks- 1].size -= CONFIG_TRUSTZONE_RESERVED_DRAM;
#endif
}
PHYS_SDRAM_1与PHYS_SDRAM_1_SIZE宏都在include/configs/x4412.h中定义。
/*DRAM Base */
#defineCONFIG_SYS_SDRAM_BASE 0x40000000
#defineSDRAM_BANK_SIZE 0x10000000 /* 256 MB */
#definePHYS_SDRAM_1 CONFIG_SYS_SDRAM_BASE /* SDRAM Bank #1 */
#definePHYS_SDRAM_1_SIZE SDRAM_BANK_SIZE
#definePHYS_SDRAM_2 (CONFIG_SYS_SDRAM_BASE + SDRAM_BANK_SIZE) /*SDRAM Bank #2 */
#definePHYS_SDRAM_2_SIZE SDRAM_BANK_SIZE
#definePHYS_SDRAM_3 (CONFIG_SYS_SDRAM_BASE + 2 * SDRAM_BANK_SIZE)/* SDRAM Bank #3 */
#definePHYS_SDRAM_3_SIZE SDRAM_BANK_SIZE
#definePHYS_SDRAM_4 (CONFIG_SYS_SDRAM_BASE + 3 * SDRAM_BANK_SIZE)/* SDRAM Bank #4 */
#definePHYS_SDRAM_4_SIZE SDRAM_BANK_SIZE
#definePHYS_SDRAM_5 (CONFIG_SYS_SDRAM_BASE + 4 * SDRAM_BANK_SIZE)/* SDRAM Bank #5 */
#definePHYS_SDRAM_5_SIZE SDRAM_BANK_SIZE
#definePHYS_SDRAM_6 (CONFIG_SYS_SDRAM_BASE + 5 * SDRAM_BANK_SIZE)/* SDRAM Bank #6 */
#definePHYS_SDRAM_6_SIZE SDRAM_BANK_SIZE
#definePHYS_SDRAM_7 (CONFIG_SYS_SDRAM_BASE + 6 * SDRAM_BANK_SIZE)/* SDRAM Bank #7 */
#definePHYS_SDRAM_7_SIZE SDRAM_BANK_SIZE
#definePHYS_SDRAM_8 (CONFIG_SYS_SDRAM_BASE + 7 * SDRAM_BANK_SIZE)/* SDRAM Bank #8 */
#definePHYS_SDRAM_8_SIZE SDRAM_BANK_SIZE
c、传递到内核的参数列表的构建
./common/cmd_bootm.c文件中,bootm命令对应的do_bootm函数,当分析uImage中信息发现OS是Linux时,调用arch/arm/lib/bootm.c文件中的do_bootm_linux函数来启动Linuxkernel。在do_bootm_linux函数中(arch/arm/lib/bootm.c),以下是部分相关源码:
#ifdefined (CONFIG_SETUP_MEMORY_TAGS) || \
defined(CONFIG_CMDLINE_TAG) || \
defined(CONFIG_INITRD_TAG) || \
defined(CONFIG_SERIAL_TAG) || \
defined(CONFIG_REVISION_TAG)
setup_start_tag(bd);
/*设置ATAG_CORE标志*/
#ifdefCONFIG_SERIAL_TAG
setup_serial_tag(¶ms);
#endif
#ifdefCONFIG_REVISION_TAG
setup_revision_tag(¶ms);
#endif
#ifdefCONFIG_SETUP_MEMORY_TAGS
setup_memory_tags(bd); /* 设置内存标记 */
#endif
#ifdefCONFIG_CMDLINE_TAG
setup_commandline_tag(bd, commandline);
/*设置命令行标记 */
#endif
#ifdefCONFIG_INITRD_TAG
if(images->rd_start && images->rd_end)
setup_initrd_tag(bd, images->rd_start, images->rd_end);
#endif
setup_end_tag(bd);
/*设置ATAG_NONE标志*/
#endif
在uboot中,进行设置传递到内核的参数列表tag的函数都在arch/arm/lib/bootm.c中,在这些函数前面是有ifdef的因此,如果你的bootm命令不能传递内核参数,就应该是在你的board的config文件里没有对上述的宏进行设置,定义一下即可。这里对于setup_start_tag、setup_memory_tags和setup_end_tag函数说明如下。它们都在arch/arm/lib/bootm.c文件中定义,如下
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);
}
标记列表必须以ATAG_CORE开始,setup_start_tag函数在内核的参数的开始地址设置了一个ATAG_CORE标记。
#ifdefCONFIG_SETUP_MEMORY_TAGS
intnr_dram_banks = -1;
static void setup_memory_tags (bd_t *bd) //初始化内存相关tag
{
inti;
for(i = 0; i < 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 */
setup_memory_tags函数设置了一个ATAG_MEM标记,该标记包含内存起始地址,内存大小这两个参数。RAM相关参数在前面的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;
}
这个静态的链表必须以标记ATAG_CORE开始,并以标记ATAG_NONE结束。setup_end_tag函数设置了一个ATAG_NONE标记,表示标记列表的结束。
d、最后do_bootm_linux函数调用theKernel(0, machid,bd->bi_boot_params)去启动内核并传递参数,可以看见r1是machid,r2是bi_boot_params参数的地址。