浅谈ARM下U-boot给Kernel传参数

我们可能都知道:U-boot会给Linux Kernel传递很多参数,如:串口波特率,RAM Size,videofb、MAC Address等,而且Linux kernel也会读取和处理这些参数。

两者之间通过struct tag来传递参数。U-boot把要传递给kernel的东西保存在struct tag数据结构中,启动kernel时,把这个结构体的物理地址传给kernel;

Linux kernel通过这个地址,用parse_tags分析出传递过来的参数。

大家也知道在ARM架构上,u-boot向Linux内核传递参数利用了R0,R1和R2三个寄存器,并采用如下约定:

R0

暂时不用,缺省放0

R1

机器号,标识计算机系统的型号,

内核支持的所有使用ARM处理器的设备ID号定义在arch/arm/tools/mach-types文件中,

编译内核过程中会被转换为一个头文件include/asm-arm/mach-types.h供其他文件包含使用。

R2

R2寄存器传递的是一个地址,也就是指针的概念,这个指针指向一个TAG区域.

UBOOT和Linux内核之间正是通过这个扩展了的TAG区域来进行复杂参数的传递,

如 command line,文件系统信息等等,用户也可以扩展这个TAG来进行更多参数的传递。

下面就一下AM335X SDK6.0的Code进行分析:

我们先了解这些参数是如何组织、如何传

先看U-boot阶段的存储的划分

 

要传递的参数上图中的SDRAM区域的什么位置?从上面的名字可以知道可定是GD区域,当然是怎么放,如何组织该这些数据,肯定要有一个相应的struct来保存,

这就是GD struct GD struct的主要成员如下:

在U-Boot的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 */
    void             **jt;        /* jump table */
} gd_t;

 

u-boot是没有MMU,这都了解,而且U-boot有重载机制,可能肯定要对GD数据Modify,这就需要记住该地址,

uboot为了方便要访问GD,特点给提供一个存储寄存器

#define DECLARE_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)

这样uboot阶段Code都可以找到GD并Modify里面的内容

那现在就看uboot如何把组织的Data给Kernel Uboot load kernel到memory中,

最长用的command是bootm bootm是Uboot启动内核的指令,它用来加载内核镜像,和go命令类似,但是支持r0,r1,r2和bootargs传递参数

bootm就是最终看到的函数是:

bootm的实现位于common/cmd_bootm.c中do_bootm函数

do_bootm的函数第一部分很简单,它首先查看环境变量"verify",如果不为"n",那么将对镜像进行Checksum的校验。

do_bootm可以接受一个可选参数,即镜像文件在内存中的地址。

如果没有指明addr,那么将使用默认的load_addr,它在早些时候被赋值为CFG_LOAD_ADDR。

再这里牵扯到Load地址,实际要加一个小插曲,就是我们Load的Kernel都是包含了一个Head,说白都是经过mkimage包装的,

当然AM335X的u-boot也是包装,因为他是两级Bootload,这不多说,就讲一下mkimage参数:

 mkimage -n "Kernel 3.2.0" -A arm -O linux -T kernel -C none -a 80007fc0 -e 80008000 -d XXX.bin uImage 实际我吗只要只要-e这个参数就可以,

这个就是do_bootm()中的load地址 继续追踪,只要do_bootm()load的是一个有Head的Image,

当然要解析这个Image,到底是那个类型,335X要要注意,应该U-boot是mkiamge包过的原因,

如果是Kernel Image,根据类型判断,就要到目标了,

实际就是do_bootm_linux()函数 这个函数重要的原因就我们刚开始讲的U-boot和Kernel直接是通过TAG传递参数,

但是TAG实际是属于GD的,怎么样去设置TAG的参数去就这下面的参数里面写的很清楚了,重点看函数:

 

setup_start_tag (bd);
setup_serial_tag (¶ms);
setup_revision_tag (¶ms);
setup_end_tag (bd);
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
		     ulong addr, ulong *len_ptr, int verify)
{
	........
	kernel_entry = (void (*)(int, int, uint))images->ep;
	........
	r2 = gd->bd->bi_boot_params;
	........
	kernel_entry(0, machid, r2);
}			
其中要注意gd->bd->bi_boot_params 这个地址是在board_init()函数里面赋值的,
就是和你的Board以及很紧密,一般都在XXXXX.h中定义 一个值得注意的参数是theKernel,
它有三个参数,并且用它来指向系统镜像的入口地址,到现在总算把参数给放到制定的位置了,也是按照ARM的规范放到了
Kernel如何取参数和解析参数
Kernel取参数,我们就仅仅说明如何去取,就不讲如何解析这些参数了arch/arm/boot/compressed/head.S中的start入口(kernel code)
start:
	.type   start,#function
	.rept   8
	mov     r0, r0
	.endr
......
1:mov     r7, r1                  @ save architecture ID
  mov     r8, r2                  @ save atags pointer
到现在总算找到参数的地址了,下面就看按照约定好的套路去解析就可以了
整个解析的过程,我们仅仅说明函数的流程:
对于 Linux Kernel , ARM 平台启动时,先执行 arch/arm/kernel/head.S ,
此文件会调用 arch/arm/kernel/head-common.S 中的函数,并最后调用 start_kernel :
...... 
b     start_kernel 
......
init/main.c 中的 start_kernel 函数中会调用 setup_arch 函数来处理各种平台相关的动作,
包括了 u-boot 传递过来参数的分析和保存
start_kernel() 
{ 
...... 
       setup_arch(&command_line); 
...... 
}
setup_arch 函数在 arch/arm/kernel/setup.c 文件中实现
parse_cmdline(cmdline_p, from);  // 处理编译内核时指定的 cmdline 或 u-boot 传递的 cmdline
到目前位置可以说已经OK了,就不多说了
解析过程中主要是TAG,下面补充一下TAG的东西,就是对TAG的分类:
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE	0x00000000
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE	0x54410001
/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM	0x54410002
/* VGA text type displays */
#define ATAG_VIDEOTEXT	0x54410003
/* describes how the ramdisk will be used in kernel */
#define ATAG_RAMDISK	0x54410004
/*
 * this one accidentally used virtual addresses - as such,
 * it's deprecated.
 */
#define ATAG_INITRD	0x54410005
/* describes where the compressed ramdisk image lives (physical address) */
#define ATAG_INITRD2	0x54420005
/* board serial number. "64 bits should be enough for everybody" */
#define ATAG_SERIAL	0x54410006
/* board revision */
#define ATAG_REVISION	0x54410007
/* initial values for vesafb-type framebuffers. see struct screen_info
 * in include/linux/tty.h
 */
#define ATAG_VIDEOLFB	0x54410008
/* command line: \0 terminated string */
#define ATAG_CMDLINE	0x54410009
/* acorn RiscPC specific information */
#define ATAG_ACORN	0x41000101
/* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */
#define ATAG_MEMCLK	0x41000402