u-boot向linux内核传递启动参数(详细) 蔚落 2022-06-10 06:40 158阅读 0赞 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
还没有评论,来说两句吧...