我们可能都知道: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 8mov 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_NONE0x00000000/* The list must start with an ATAG_CORE node */#define ATAG_CORE0x54410001/* it is allowed to have multiple ATAG_MEM nodes */#define ATAG_MEM0x54410002/* VGA text type displays */#define ATAG_VIDEOTEXT0x54410003/* describes how the ramdisk will be used in kernel */#define ATAG_RAMDISK0x54410004/* * this one accidentally used virtual addresses - as such, * it's deprecated. */#define ATAG_INITRD0x54410005/* describes where the compressed ramdisk image lives (physical address) */#define ATAG_INITRD20x54420005/* board serial number. "64 bits should be enough for everybody" */#define ATAG_SERIAL0x54410006/* board revision */#define ATAG_REVISION0x54410007/* initial values for vesafb-type framebuffers. see struct screen_info * in include/linux/tty.h */#define ATAG_VIDEOLFB0x54410008/* command line: terminated string */#define ATAG_CMDLINE0x54410009/* acorn RiscPC specific information */#define ATAG_ACORN0x41000101/* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */#define ATAG_MEMCLK0x41000402