uboot启动参数传递和内核调用

一、启动参数传递过程

  U-Boot使用命令bootm来启动已经加载到内存中的内核。而bootm命令实际上调用的是do_bootm函数。
        以bootm命令启动内核为例,bootm命令实际上调用的是do_bootm_linux函数:
  内核调用函数:theKernel (0,bd->bi_arch_number, bd->bi_boot_params); 
the kernel其实不是个函数,而是指向内核入口地址的指针,把它强行转化为带三个参数的函数指针,会把三个参数保存到通用寄存器中,实现了向kernel传递信息的功能,在这个例子里,会把R0赋值为0,R1赋值为机器号 R2赋值为启动参数数据结构的首地址。 
因此,要向内核传递参数很简单,只要把启动参数封装在linux预定好的数据结构里,拷贝到某个地址(一般约定俗成是内存首地址+0x100) 。在tx2440开发板,该地址为 0x3000 0000+0x100。

二、启动参数数据结构

启动参数可保存在两种数据结构中,param_structtag,前者是2.4内核用的,后者是2.6以后的内核更期望用的但是,到目前为止,2.6的内核也可以兼容前一种结构,两种数据结构具体定义如下在U-Boot源代码目录include/asm-arm/setup.h。

2.1 param_struct数据结构(适用2.4内核用的)

struct param_struct {
  union 
{ 
     struct 
{ 
         unsigned long page_size;        /*  0 */ 
         unsigned long nr_pages;        /*  4 */ 
         unsigned long ramdisk_size;        /*  8 */ 
   。。。。。。。。。。。。。。。。。。。。。
         unsigned long mem_fclk_21285;       /* 88 */ 
     } s; 
     char unused[256]; 
    } u1; 
     union 
{ 
    	char paths[8][128]; 
     	struct 
{ 
       unsigned long magic; 
       char n[1024 - sizeof(unsigned long)]; 
     	} s; 
     } u2; 
     char commandline[COMMAND_LINE_SIZE]; 
}; 
param_struct只需要设置cmmandline,u1.s.page_size,u1.s.nr_pages三个域,具体使用可参见下面的例子。

2.2 tag数据结构(适用2.6内核用的)

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;        //Acorn specific 
     struct tag_omap   omap;     //OMAP specific 
         struct tag_memclk    memclk;   //DC21285 specific 
     } u; 
}; 
对于tag来说,在实际使用中是一个struct tag组成的列表,tag->tag_header,一项是u32 tag(重名,注意类型)其值用宏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,另一项是u32 size,表示tag的大小,tag组成列表的方式就是指针+size,实际使用中用tag_next (params).tag的具体使用的例子

需要注意的是,这两个数据结构在uboot中和linux中分别有定义,这个定义必须一致才能正常传递参数如果实际使用中不一致的话就不能正常传递,可以自行修改。

三、两种数据结构传递启动参数例子

1:例子一:通过param_structuboot中的go命令可以传递参数
分析:go的代码在common/cmd_boot.c,里面并没有拷贝启动参数的代码,转向内核的时候也没有传送启动参数所在的地址,因此添加如下代码用于拷贝参数,可以看到,对于param_struct只需要设置cmmandlineu1.s.page_sizeu1.s.nr_pages三个域
   char *commandline =getenv("bootargs");通过环境变量bootargs, 获取传递给内核启动参数
  struct param_struct *lxy_params=(struct param_struct *)0x30000100;
设置地址

    printf("setup linux parameters at 0x30000100\n");
    memset(lxy_params,0,sizeof(struct param_struct)); lxy_params
区域清零

   lxy_params->u1.s.page_size=(0x1<<12); //4K这个是必须有的,否则无法启动
   lxy_params->u1.s.nr_pages=(0x4000000)>>12; //64M这个是必须有的,否则无法启动

    memcpy(lxy_params->commandline,commandline,strlen(commandline)+1);

然后还要向内核传递参数地址,
rc = ((ulong(*)(int,int,uint))addr) (0,gd->bd->bi_arch_number,gd->bd->bi_boot_params);//调用内核
  
2:例子二:bootm命令中通过拷贝tag传递参数
为方便阅读,进行了少许修改,但功能不变,该函数参数为存放启动参数的地址
static void setup_linux_tag(ulong param_base)

    struct tag *params = (struct tag *)param_base;
   char *linux_cmd; 
  char *p; 
   memset(params, 0, sizeof(struct tag)); 
 
 /* step1: setup start tag */ 
    params->hdr.tag = ATAG_CORE;
   params->hdr.size = tag_size(tag_core); 
   params->u.core.flags = 0; 
   params->u.core.pagesize = LINUX_PAGE_SIZE;
   params->u.core.rootdev = 0; 
    params = tag_next(params); 
  
/* step2: setup cmdline tag */ 
   params->hdr.tag = ATAG_CMDLINE;
   linux_cmd = getenv("bootargs"); 
/* eat leading white space */ 
   for (p=linux_cmd; *p==' '; p++) {/* do nothing */;}
   params->hdr.size = (sizeof(structtag_header)+strlen(linux_cmd)+1+4) >> 2;
   memcpy(params->u.cmdline.cmdline, linux_cmd,strlen(linux_cmd)+1);
    params = tag_next(params); 
  
/* step3: setup end tag */ 
  params->hdr.tag = ATAG_NONE;
   params->hdr.size = 0; 
}


四、调用内核前状态

U-Boot设置好标记列表后就要调用内核了。但调用内核前,CPU必须满足下面的条件:

(1)    CPU寄存器的设置

Ø r0=0

Ø r1=机器码

Ø r2=内核参数标记列表在RAM中的起始地址

(2)    CPU工作模式

Ø 禁止IRQ与FIQ中断

Ø CPU为SVC模式

(3)    使数据Cache与指令Cache失效

     do_bootm_linux中调用的cleanup_before_linux函数完成了禁止中断和使Cache失效的功能。cleanup_before_linux函数在cpu/arm920t/cpu.中定义:

intcleanup_before_linux (void)

{      disable_interrupts ();         /* 禁止FIQ/IRQ中断 */

      /* turn off I/D-cache */

      icache_disable();              /* 使指令Cache失效 */

      dcache_disable();             /* 使数据Cache失效 */

      /* flush I/D-cache */

      cache_flush();                   /* 刷新Cache */

      return 0;

}

      由于U-Boot启动以来就一直工作在SVC模式,因此CPU的工作模式就无需设置了。

do_bootm_linux中:

64      void       (*theKernel)(intzero, int arch, uint params);

……

73      theKernel = (void (*)(int, int, uint))images->ep;

……

128     theKernel (0, machid, bd->bi_boot_params);

      第73行代码将内核的入口地址“images->ep”强制类型转换为函数指针。根据ATPCS规则,函数的参数个数不超过4个时,使用r0~r3这4个寄存器来传递参数。因此第128行的函数调用则会将0放入r0,机器码machid放入r1,内核参数地址bd->bi_boot_params放入r2,从而完成了寄存器的设置,最后转到内核的入口地址。

      到这里,U-Boot的工作就结束了,系统跳转到Linux内核代码执行。

你可能感兴趣的:(linux内核开发基础)