cmdline传递

ubootkernel的传参机制——bootmtags

 

一uboot 如何启动 kernel

1 do_bootm

uboot下使用bootm命令启动内核镜像文件uImage,uImage是在zImage头添加了64字节的镜像信息供uboot解析使用,具体这64字节头的内容,我们在分析bootm命令的时候就会一一说到,那直接来看bootm命令。

在common/cmd_bootm.c中

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])  
  1. {  
  1. #ifdef CONFIG_NEEDS_MANUAL_RELOC  
  1.     static int relocated = 0;  
  2.   
  3.     if (!relocated) {  
  4.         int i;  
  5.   
  6.         /* relocate boot function table */  
  7.         for (i = 0; i < ARRAY_SIZE(boot_os); i++)  
  8.             if (boot_os[i] != NULL)  
  9.                 boot_os[i] += gd->reloc_off;  
  10.   
  11.         /* relocate names of sub-command table */  
  12.         for (i = 0; i < ARRAY_SIZE(cmd_bootm_sub); i++)  
  13.             cmd_bootm_sub[i].name += gd->reloc_off;  
  14.   
  15.         relocated = 1;  
  16.     }  
  1. #endif  
  1.     /* determine if we have a sub command */  
  2.     argc--; argv++;  
  3.     if (argc > 0) {  
  4.         char *endp;  
  5.   
  6.         simple_strtoul(argv[0], &endp, 16);  
  7.         /* endp pointing to NULL means that argv[0] was just a 
  1.          * valid number, pass it along to the normal bootm processing 
  2.          * 
  3.          * If endp is ':' or '#' assume a FIT identifier so pass 
  4.          * along for normal processing. 
  5.          * 
  6.          * Right now we assume the first arg should never be '-' 
  7.          */  
  1.         if ((*endp != 0) && (*endp != ':') && (*endp != '#'))  
  2.             return do_bootm_subcommand(cmdtp, flag, argc, argv);  
  3.     }  
  4.   
  5.     return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |  
  6.         BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |  
  7.         BOOTM_STATE_LOADOS |  
  1. #if defined(CONFIG_PPC) || defined(CONFIG_MIPS)  
  1.         BOOTM_STATE_OS_CMDLINE |  
  1. #endif  
  1.         BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |  
  2.         BOOTM_STATE_OS_GO, &images, 1);  
  3. }  

数组boot_os是bootm最后阶段启动kernel时调用的函数数组,CONFIG_NEEDS_MANUAL_RELOC中的代码含义是将boot_os函数都进行偏移(uboot启动中会将整个code拷贝到靠近sdram顶端的位置执行),

但是boot_os函数在ubootrelocate时已经都拷贝了,所以感觉没必要在进行relocate。这个宏因此没有定义,直接走下面。

新版uboot对于bootkernel实现了一个类似状态机的机制,将整个过程分成很多个阶段,uboot将每个阶段称为subcommand,

核心函数是do_bootm_states,需要执行哪个阶段,就在do_bootm_states最后一个参数添加那个宏定义,如: BOOTM_STATE_START

do_bootm_subcommand是按照bootm参数来指定运行某一个阶段,也就是某一个subcommand

对于正常的uImage,bootm加tftp的load地址就可以。

2do_bootm_states

这样会走到最后函数do_bootm_states,那就来看看核心函数do_bootm_states

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc,  
  1.         char * const argv[], int states, bootm_headers_t *images,  
  2.         int boot_progress)  
  3. {  
  4.     boot_os_fn *boot_fn;  
  5.     ulong iflag = 0;  
  6.     int ret = 0, need_boot_fn;  
  7.   
  8.     images->state |= states;  
  9.   
  10.     /* 
  1.      * Work through the states and see how far we get. We stop on 
  2.      * any error. 
  3.      */  
  1.     if (states & BOOTM_STATE_START)  
  2.         ret = bootm_start(cmdtp, flag, argc, argv);  

参数中需要注意bootm_headers_t*images,这个参数用来存储由image头64字节获取到的的基本信息。由do_bootm传来的该参数是images,是一个全局的静态变量。

首先将states存储在images的state中,因为states中有BOOTM_STATE_START,调用bootm_start.

 

3第一阶段:bootm_start

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])  
  1. {  
  2.     memset((void *)&images, 0, sizeof(images));  
  3.     images.verify = getenv_yesno("verify");  
  4.   
  5.     boot_start_lmb(&images);  
  6.   
  7.     bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");  
  8.     images.state = BOOTM_STATE_START;  
  9.   
  10.     return 0;  
  11. }  

获取verify,bootstage_mark_name标志当前状态为bootmstart(bootstage_mark_name可以用于无串口调试,在其中实现LED控制)。

boot_start_lmb暂时还没弄明白,以后再搞清楚。

最后修改images.state为bootmstart。

bootm_start主要工作是清空images,标志当前状态为bootmstart。

 

4第二阶段:bootm_find_os

由bootm_start返回后,do_bootm传了BOOTM_STATE_FINDOS,所以进入函数bootm_find_os

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,  
  1.              char * const argv[])  
  2. {  
  3.     const void *os_hdr;  
  4.   
  5.     /* get kernel image header, start address and length */  
  6.     os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,  
  7.             &images, &images.os.image_start, &images.os.image_len);  
  8.     if (images.os.image_len == 0) {  
  9.         puts("ERROR: can't get kernel image!\n");  
  10.         return 1;  
  11.     }  

调用boot_get_kernel,函数较长,首先是获取image的load地址,如果bootm有参数,就是img_addr,之后如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. bootstage_mark(BOOTSTAGE_ID_CHECK_MAGIC);  
  2.   
  1. /* copy from dataflash if needed */  
  1. img_addr = genimg_get_image(img_addr);  
  2.   
  1. /* check image type, for FIT images get FIT kernel node */  
  1. *os_data = *os_len = 0;  
  2. buf = map_sysmem(img_addr, 0);  

首先标志当前状态,然后调用genimg_get_image,该函数会检查当前的img_addr是否在sdram中,如果是在flash中,则拷贝到sdram中CONFIG_SYS_LOAD_ADDR处,修改img_addr为该地址。

这里说明我们的image可以在flash中用bootm直接起

map_sysmem为空函数,buf即为img_addr。

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. switch (genimg_get_format(buf)) {  
  2. case IMAGE_FORMAT_LEGACY:  
  1.     printf("## Booting kernel from Legacy Image at %08lx ...\n",  
  2.             img_addr);  
  3.     hdr = image_get_kernel(img_addr, images->verify);  
  4.     if (!hdr)  
  5.         return NULL;  
  6.     bootstage_mark(BOOTSTAGE_ID_CHECK_IMAGETYPE);  
  7.   
  8.     /* get os_data and os_len */  
  9.     switch (image_get_type(hdr)) {  
  10.     case IH_TYPE_KERNEL:  
  11.     case IH_TYPE_KERNEL_NOLOAD:  
  12.         *os_data = image_get_data(hdr);  
  13.         *os_len = image_get_data_size(hdr);  
  14.         break;  
  15.     case IH_TYPE_MULTI:  
  16.         image_multi_getimg(hdr, 0, os_data, os_len);  
  17.         break;  

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. case IH_TYPE_STANDALONE:  
  1.     *os_data = image_get_data(hdr);  
  2.     *os_len = image_get_data_size(hdr);  
  3.     break;  
  1. default:  
  1.     printf("Wrong Image Type for %s command\n",  
  2.         cmdtp->name);  
  3.     bootstage_error(BOOTSTAGE_ID_CHECK_IMAGETYPE);  
  4.     return NULL;  
  5. }  
  6.   
  1. /* 
  2.  * copy image header to allow for image overwrites during 
  3.  * kernel decompression. 
  4.  */  
  1. memmove(&images->legacy_hdr_os_copy, hdr,  
  2.     sizeof(image_header_t));  
  3.   
  1. /* save pointer to image header */  
  1. images->legacy_hdr_os = hdr;  
  2.   
  3. images->legacy_hdr_valid = 1;  
  4. bootstage_mark(BOOTSTAGE_ID_DECOMP_IMAGE);  
  1. break;  

首先来说明一下imageheader的格式,在代码中由image_header_t代表,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. typedef struct image_header {  
  1.     __be32      ih_magic;   /* Image Header Magic Number    */   
  2.     __be32      ih_hcrc;    /* Image Header CRC Checksum    */  
  3.     __be32      ih_time;    /* Image Creation Timestamp */  
  4.     __be32      ih_size;    /* Image Data Size      */  
  5.     __be32      ih_load;    /* Data  Load  Address      */  
  6.     __be32      ih_ep;      /* Entry Point Address      */  
  7.     __be32      ih_dcrc;    /* Image Data CRC Checksum  */  
  8.     uint8_t     ih_os;      /* Operating System     */  
  9.     uint8_t     ih_arch;    /* CPU architecture     */  
  10.     uint8_t     ih_type;    /* Image Type           */  
  11.     uint8_t     ih_comp;    /* Compression Type     */  
  12.     uint8_t     ih_name[IH_NMLEN];  /* Image Name       */  
  13. } image_header_t;  

genimg_get_format检查imgheader的头4个字节,代表image的类型,有2种,legacy和FIT,这里使用的legacy,头4个字节为0x27051956。

image_get_kernel则会来计算header的crc是否正确,然后获取image的type,根据type来获取os的len和data起始地址。

最后将hdr的数据拷贝到images的legacy_hdr_os_copy,防止kernelimage在解压是覆盖掉hdr数据,保存hdr指针到legacy_hdr_os中,置位legacy_hdr_valid。

从boot_get_kernel中返回到bootm_find_os,继续往下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. switch (genimg_get_format(os_hdr)) {  
  2. case IMAGE_FORMAT_LEGACY:  
  1.     images.os.type = image_get_type(os_hdr);  
  2.     images.os.comp = image_get_comp(os_hdr);  
  3.     images.os.os = image_get_os(os_hdr);  
  4.   
  5.     images.os.end = image_get_image_end(os_hdr);  
  6.     images.os.load = image_get_load(os_hdr);  

根据hdr获取os的type,comp,os,end,loadaddr。

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. /* find kernel entry point */  
  1. if (images.legacy_hdr_valid) {  
  1.     images.ep = image_get_ep(&images.legacy_hdr_os_copy);  
  2. else {  
  3.     puts("Could not find kernel entry point!\n");  
  4.     return 1;  
  5. }  
  6.   
  1. if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {  
  1.     images.os.load = images.os.image_start;  
  2.     images.ep += images.os.load;  
  3. }  
  4.   
  5. images.os.start = (ulong)os_hdr;  

获取os的start。

到这里bootm_find_os就结束了,主要工作是根据image的hdr来做crc,获取一些基本的os信息到images结构体中。

 

回到do_bootm_states中接下来调用bootm_find_other,

 

5第三阶段:bootm_find_other

该函数大体看一下,对于legacy类型的image,获取查询是否有ramdisk,此处我们没有用单独的ramdisk,ramdisk是直接编译到kernelimage中的。

回到do_bootm_states中接下来会调用bootm_load_os。

 

6第四阶段:bootm_load_os

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,  
  1.         int boot_progress)  
  2. {  
  3.     image_info_t os = images->os;  
  4.     uint8_t comp = os.comp;  
  5.     ulong load = os.load;  
  6.     ulong blob_start = os.start;  
  7.     ulong blob_end = os.end;  
  8.     ulong image_start = os.image_start;  
  9.     ulong image_len = os.image_len;  
  10.     __maybe_unused uint unc_len = CONFIG_SYS_BOOTM_LEN;  
  11.     int no_overlap = 0;  
  12.     void *load_buf, *image_buf;  
  1. #if defined(CONFIG_LZMA) || defined(CONFIG_LZO)  
  1.     int ret;  
  1. #endif /* defined(CONFIG_LZMA) || defined(CONFIG_LZO) */  
  1.   
  2.     const char *type_name = genimg_get_type_name(os.type);  
  3.   
  4.     load_buf = map_sysmem(load, unc_len);  
  5.     image_buf = map_sysmem(image_start, image_len);  
  6.     switch (comp) {  
  7.     case IH_COMP_NONE:  
  8.         if (load == blob_start || load == image_start) {  
  9.             printf("   XIP %s ... ", type_name);  
  10.             no_overlap = 1;  
  11.         } else {  
  12.             printf("   Loading %s ... ", type_name);  
  13.             memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);  
  14.         }  
  15.         *load_end = load + image_len;  
  16.         break;  

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. #ifdef CONFIG_GZIP  
  1.     case IH_COMP_GZIP:  
  2.         printf("   Uncompressing %s ... ", type_name);  
  3.         if (gunzip(load_buf, unc_len, image_buf, &image_len) != 0) {  
  4.             puts("GUNZIP: uncompress, out-of-mem or overwrite "  
  5.                 "error - must RESET board to recover\n");  
  6.             if (boot_progress)  
  7.                 bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);  
  8.             return BOOTM_ERR_RESET;  
  9.         }  
  10.   
  11.         *load_end = load + image_len;  
  12.         break;  
  1. #endif /* CONFIG_GZIP */  

load_buf是之前find_os是根据hdr获取的loadaddr,image_buf是find_os获取的image的开始地址(去掉64字节头)。

之后则是根据hdr的comp类型来解压拷贝image到loadaddr上。

这里就需要注意,kernel选项的压缩格式必须在uboot下打开相应的解压缩支持,或者就不进行压缩

这里还有一点,loadaddr与image add是否可以重叠,看代码感觉是可以重叠的,还需要实际测试一下。

回到do_bootm_states,接下来根据os从boot_os数组中获取到了相应的osboot func,这里是linux,则是do_bootm_linux。后面代码如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. /* Call various other states that are not generally used */  
  1. if (!ret && (states & BOOTM_STATE_OS_CMDLINE))  
  1.     ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);  
  1. if (!ret && (states & BOOTM_STATE_OS_BD_T))  
  1.     ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);  
  1. if (!ret && (states & BOOTM_STATE_OS_PREP))  
  1.     ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);  
  2.   
  1. /* Check for unsupported subcommand. */  
  1. if (ret) {  
  1.     puts("subcommand not supported\n");  
  2.     return ret;  
  3. }  
  4.   
  1. /* Now run the OS! We hope this doesn't return */  
  1. if (!ret && (states & BOOTM_STATE_OS_GO))  
  1.     ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,  
  2.             images, boot_fn);  

这时do_bootm最后的代码,如果正常,bootkernel之后就不应该回来了。states中定义了BOOTM_STATE_OS_PREP(对于mips处理器会使用BOOTM_STATE_OS_CMDLINE),调用do_bootm_linux,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)  
  1. {  
  2.     /* No need for those on ARM */  
  3.     if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)  
  4.         return -1;   
  5.   
  6.     if (flag & BOOTM_STATE_OS_PREP) {  
  7.         boot_prep_linux(images);  
  8.         return 0;  
  9.     }     
  10.   
  11.     if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {  
  12.         boot_jump_linux(images, flag);  
  13.         return 0;  
  14.     }     
  15.   
  16.     boot_prep_linux(images);  
  17.     boot_jump_linux(images, flag);  
  18.     return 0;  
  19. }  

do_bootm_linux实现跟do_bootm类似,也是根据flag分阶段运行subcommand,这里会调到boot_prep_linux。

 

7第五阶段:boot_prep_linux

该函数作用是为启动后的kernel准备参数,这个函数我们在第三部分uboot如何传参给kernel再仔细分析一下

boot_prep_linux完成返回到do_bootm_states后接下来就是最后一步了。执行boot_selected_os调用do_bootm_linux,flag为BOOTM_STATE_OS_GO,则调用boot_jump_linux

 

8第六阶段:boot_jump_linux

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. unsigned long machid = gd->bd->bi_arch_number;  
  1. char *s;  
  1. void (*kernel_entry)(int zero, int arch, uint params);  
  1. unsigned long r2;  
  1. int fake = (flag & BOOTM_STATE_OS_FAKE_GO);  
  1.   
  2. kernel_entry = (void (*)(intint, uint))images->ep;  
  3.   
  4. s = getenv("machid");  
  1. if (s) {  
  1.     strict_strtoul(s, 16, &machid);  
  2.     printf("Using machid 0x%lx from environment\n", machid);  
  3. }  
  4.   
  5. debug("## Transferring control to Linux (at address %08lx)" \  
  6.     "...\n", (ulong) kernel_entry);  
  7. bootstage_mark(BOOTSTAGE_ID_RUN_OS);  
  8. announce_and_cleanup(fake);  
  9.   
  1. if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)  
  1.     r2 = (unsigned long)images->ft_addr;  
  1. else  
  1.     r2 = gd->bd->bi_boot_params;  
  2.   
  1. if (!fake)  
  1.     kernel_entry(0, machid, r2);  

boot_jump_linux主体函数如上

获取gd->bd->bi_arch_number为machid,如果有env则用env的machid,kernel_entry为之前由hdr获取的ep,也就是内核的入口地址。

fake为0,直接调用kernel_entry,参数1为0,参数2为machid,参数3为bi_boot_params。

 

这之后就进入了kernel的执行流程启动,就不会再回到uboot

这整个boot过程中bootm_images_t一直作为对image信息的全局存储结构。

 

三uboot如何传参给kernel

uboot下的传参机制就直接来分析boot_prep_linux函数就可以了,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static void boot_prep_linux(bootm_headers_t *images)  
  1. {  
  2.     char *commandline = getenv("bootargs");  
  3.   
  4.     if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {  
  1. #ifdef CONFIG_OF_LIBFDT  
  1.         debug("using: FDT\n");  
  2.         if (image_setup_linux(images)) {  
  3.             printf("FDT creation failed! hanging...");  
  4.             hang();  
  5.         }  
  1. #endif  
  1.     } else if (BOOTM_ENABLE_TAGS) {  
  2.         debug("using: ATAGS\n");  
  3.         setup_start_tag(gd->bd);  
  4.         if (BOOTM_ENABLE_SERIAL_TAG)  
  5.             setup_serial_tag(¶ms);  
  6.         if (BOOTM_ENABLE_CMDLINE_TAG)  
  7.             setup_commandline_tag(gd->bd, commandline);  
  8.         if (BOOTM_ENABLE_REVISION_TAG)  
  9.             setup_revision_tag(¶ms);  
  10.         if (BOOTM_ENABLE_MEMORY_TAGS)  
  11.             setup_memory_tags(gd->bd);  
  12.         if (BOOTM_ENABLE_INITRD_TAG) {  
  13.             if (images->rd_start && images->rd_end) {  
  14.                 setup_initrd_tag(gd->bd, images->rd_start,  
  15.                          images->rd_end);  
  16.             }  
  17.         }  
  18.         setup_board_tags(¶ms);  
  19.         setup_end_tag(gd->bd);  
  20.     } else {  
  21.         printf("FDT and ATAGS support not compiled in - hanging\n");  
  22.         hang();  
  23.     }  
  24.     do_nonsec_virt_switch();  
  25. }  

首先获取出环境变量bootargs,这就是要传递给kernel的参数。

在配置文件中定义了CONFIG_CMDLINE_TAG以及CONFIG_SETUP_MEMORY_TAGS,根据arch/arm/include/asm/bootm.h,则会定义BOOTM_ENABLE_TAGS,首先调用setup_start_tag,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static void setup_start_tag (bd_t *bd)  
  1. {         
  2.     params = (struct tag *)bd->bi_boot_params;  
  3.           
  4.     params->hdr.tag = ATAG_CORE;  
  5.     params->hdr.size = tag_size (tag_core);  
  6.               
  7.     params->u.core.flags = 0;  
  8.     params->u.core.pagesize = 0;  
  9.     params->u.core.rootdev = 0;  
  10.               
  11.     params = tag_next (params);  
  12. }             

params是一个全局静态变量用来存储要传给kernel的参数,这里bd->bi_boot_params的值赋给params,因此bi_boot_params需要进行初始化,从而将params放在一个合理的内存区域。

这里params为structtag的结构,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. struct tag {  
  1.     struct tag_header hdr;  
  2.     union {  
  3.         struct tag_core     core;  
  4.         struct tag_mem32    mem;  
  5.         struct tag_videotext    videotext;  
  6.         struct tag_ramdisk  ramdisk;  
  7.         struct tag_initrd   initrd;  
  8.         struct tag_serialnr serialnr;  
  9.         struct tag_revision revision;  
  10.         struct tag_videolfb videolfb;  
  11.         struct tag_cmdline  cmdline;  
  12.   
  13.         /* 
  1.          * Acorn specific 
  2.          */  
  1.         struct tag_acorn    acorn;  
  2.   
  3.         /* 
  1.          * DC21285 specific 
  2.          */  
  1.         struct tag_memclk   memclk;  
  2.     } u;  
  3. };  

tag包括hdr和各种类型的tag_*,hdr来标志当前的tag是哪种类型的tag。

setup_start_tag是初始化了第一个tag,是tag_core类型的tag。最后调用tag_next跳到第一个tag末尾,为下一个tag做准备。

回到boot_prep_linux,接下来调用setup_commandline_tag,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static void setup_commandline_tag(bd_t *bd, char *commandline)  
  1. {             
  2.     char *p;  
  3.               
  4.     if (!commandline)  
  5.         return;  
  6.           
  7.     /* eat leading white space */  
  8.     for (p = commandline; *p == ' '; p++);  
  9.               
  10.     /* skip non-existent command lines so the kernel will still 
  1.      * use its default command line. 
  2.      */       
  1.     if (*p == '\0')  
  2.         return;  
  3.           
  4.     params->hdr.tag = ATAG_CMDLINE;  
  5.     params->hdr.size =  
  6.         (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;  
  7.   
  8.     strcpy (params->u.cmdline.cmdline, p);  
  9.   
  10.     params = tag_next (params);  
  11. }  

该函数设置第二个tag的hdr.tag为ATAG_CMDLINE,然后拷贝cmdline到tags的cmdline结构体中,跳到下一个tag。

回到boot_prep_linux,调用setup_memory_tag,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static void setup_memory_tags(bd_t *bd)  
  1. {         
  2.     int i;    
  3.           
  4.     for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {  
  5.         params->hdr.tag = ATAG_MEM;  
  6.         params->hdr.size = tag_size (tag_mem32);  
  7.                   
  8.         params->u.mem.start = bd->bi_dram[i].start;  
  9.         params->u.mem.size = bd->bi_dram[i].size;  
  10.           
  11.         params = tag_next (params);  
  12.     }     
  13. }     

过程类似,将第三个tag设为ATAG_MEM,将mem的start,size保存在此处,如果有多片ram(CONFIG_NR_DRAM_BANKS> 1),则将下一个tag保存下一片ram的信息,依次类推。

回到boot_prep_linux中,调用setup_board_tags,这个函数是__weak属性,我们可以在自己的板级文件中去实现来保存跟板子相关的参数,如果没有实现,则是空函数。

最后调用setup_end_tags,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static void setup_end_tag(bd_t *bd)  
  1. {         
  2.     params->hdr.tag = ATAG_NONE;  
  3.     params->hdr.size = 0;  
  4. }         

最后将最末尾的tag设置为ATAG_NONE,标志tag结束。

 

这样整个参数的准备就结束了,最后在调用boot_jump_linux时会将tags的首地址也就是bi_boot_params传给kernel,供kernel来解析这些tag,kernel如何解析看第四部分kenrel如何找到并解析参数

 

总结一下,uboot将参数以tag数组的形式布局在内存的某一个地址,每个tag代表一种类型的参数,首尾tag标志开始和结束,首地址传给kernel供其解析。

 

四kernel如何找到并解析参数

uboot在调用boot_jump_linux时最后kernel_entry(0,machid, r2);

按照二进制规范eabi,machid存在寄存器r1,r2即tag的首地址存在寄存器r2.

查看kernel的入口函数,在arch/arm/kernel/head.S,中可以看到如下一段汇编:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. /*   
  2.  * r1 = machine no, r2 = atags or dtb, 
  3.  * r8 = phys_offset, r9 = cpuid, r10 = procinfo 
  4.  */  
  1. bl  __vet_atags  

可以看出kernel刚启动会调用__vet_atags来处理uboot传来的参数,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. __vet_atags:  
  2.     tst r2, #0x3            @ aligned?  
  3.     bne 1f  
  4.   
  5.     ldr r5, [r2, #0]  
  1. #ifdef CONFIG_OF_FLATTREE  
  1.     ldr r6, =OF_DT_MAGIC        @ is it a DTB?  
  2.     cmp r5, r6  
  3.     beq 2f  
  1. #endif  
  1.     cmp r5, #ATAG_CORE_SIZE     @ is first tag ATAG_CORE?  
  2.     cmpne   r5, #ATAG_CORE_SIZE_EMPTY  
  3.     bne 1f  
  4.     ldr r5, [r2, #4]  
  5.     ldr r6, =ATAG_CORE  
  6.     cmp r5, r6  
  7.     bne 1f  
  8.   
  9. 2:  mov pc, lr              @ atag/dtb pointer is ok  
  10.   
  11. 1:  mov r2, #0  
  12.     mov pc, lr  
  13. ENDPROC(__vet_atags)  

主要是对tag进行了一个简单的校验,查看tag头4个字节(tag_core的size)和第二个4字节(tag_core的type)。

之后对参数的真正分析处理是在start_kernel的setup_arch中,在arch/arm/kernel/setup.c中,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. void __init setup_arch(char **cmdline_p)  
  1. {  
  2.     struct machine_desc *mdesc;  
  3.   
  4.     setup_processor();  
  5.     mdesc = setup_machine_fdt(__atags_pointer);  
  6.     if (!mdesc)  
  7.         mdesc = setup_machine_tags(machine_arch_type);  
  8.     machine_desc = mdesc;  
  9.     machine_name = mdesc->name;  
  10.   
  1. #ifdef CONFIG_ZONE_DMA  
  1.     if (mdesc->dma_zone_size) {  
  2.         extern unsigned long arm_dma_zone_size;  
  3.         arm_dma_zone_size = mdesc->dma_zone_size;  
  4.     }             
  1. #endif                   
  1.     if (mdesc->restart_mode)  
  2.         reboot_setup(&mdesc->restart_mode);  
  3.       
  4.     init_mm.start_code = (unsigned long) _text;  
  5.     init_mm.end_code   = (unsigned long) _etext;  
  6.     init_mm.end_data   = (unsigned long) _edata;  
  7.     init_mm.brk    = (unsigned long) _end;  
  8.   
  9.     /* populate cmd_line too for later use, preserving boot_command_line */  
  10.     strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);  
  11.     *cmdline_p = cmd_line;  
  12.   
  13.     parse_early_param();  

关键函数是setup_machine_tags,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static struct machine_desc * __init setup_machine_tags(unsigned int nr)  
  1. {  
  2.     struct tag *tags = (struct tag *)&init_tags;  
  3.     struct machine_desc *mdesc = NULL, *p;  
  4.     char *from = default_command_line;  
  5. 。。。。  
  6.     if (__atags_pointer)  
  7.         tags = phys_to_virt(__atags_pointer);  
  8.     else if (mdesc->atag_offset)  
  9.         tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);  
  10.   
  11. 。。。。。  
  12.     if (tags->hdr.tag == ATAG_CORE) {  
  13.         if (meminfo.nr_banks != 0)  
  14.             squash_mem_tags(tags);  
  15.         save_atags(tags);  
  16.         parse_tags(tags);  
  17.     }  
  18.   
  19.     /* parse_early_param needs a boot_command_line */  
  20.     strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);  
  21. 。。。  
  22. }  

首先回去获取tags的首地址,如果收个tag是ATAG_CORE类型,则会调用save_atags拷贝一份tags,最后调用parse_tags来分析这个taglist,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static int __init parse_tag(const struct tag *tag)  
  1. {  
  2.     extern struct tagtable __tagtable_begin, __tagtable_end;  
  3.     struct tagtable *t;  
  4.   
  5.     for (t = &__tagtable_begin; t < &__tagtable_end; t++)  
  6.         if (tag->hdr.tag == t->tag) {  
  7.             t->parse(tag);  
  8.             break;  
  9.         }  
  10.       
  11.     return t < &__tagtable_end;  
  12. }     
  13.           
  1. /*   
  2.  * Parse all tags in the list, checking both the global and architecture 
  3.  * specific tag tables. 
  4.  */           
  1. static void __init parse_tags(const struct tag *t)  
  1. {         
  2.     for (; t->hdr.size; t = tag_next(t))  
  3.         if (!parse_tag(t))  
  4.             printk(KERN_WARNING  
  5.                 "Ignoring unrecognised tag 0x%08x\n",  
  6.                 t->hdr.tag);  
  7. }     

遍历tagslist,找到在tagstable中匹配的处理函数(hdr.tag一致),来处理响应的tag。

这个tagtable的处理函数是在调用__tagtable来注册的,如下:

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. static int __init parse_tag_cmdline(const struct tag *tag)  
  1. {  
  1. #if defined(CONFIG_CMDLINE_EXTEND)  
  1.     strlcat(default_command_line, " ", COMMAND_LINE_SIZE);  
  2.     strlcat(default_command_line, tag->u.cmdline.cmdline,  
  3.         COMMAND_LINE_SIZE);  
  1. #elif defined(CONFIG_CMDLINE_FORCE)  
  1.     pr_warning("Ignoring tag cmdline (using the default kernel command line)\n");  
  1. #else  
  1.     strlcpy(default_command_line, tag->u.cmdline.cmdline,  
  2.         COMMAND_LINE_SIZE);  
  1. #endif  
  1.     return 0;  
  2. }  
  3.   
  4. __tagtable(ATAG_CMDLINE, parse_tag_cmdline);  

看这个对cmdline类型的tag的处理,就是将tag中的cmdline拷贝到default_command_line中。还有其他如mem类型的参数也会注册这个处理函数,来匹配处理响应的tag。这里就先以cmdline的tag为例。

这样遍历并处理完tagslist之后回到setup_machine_tags,将from(即default_command_line)中的cmdline拷贝到boot_command_line,

最后返回到setup_arch中,

[cpp] 

view plain 

copy

 

在CODE上查看代码片

 

  1. /* populate cmd_line too for later use, preserving boot_command_line */  
  1. strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);  
  2. *cmdline_p = cmd_line;  
  3.   
  4. parse_early_param();  

将boot_command_line拷贝到start_kernel给setup_arch的cmdline_p中,这里中间拷贝的boot_command_line是给parse_early_param来做一个早期的参数分析的。

到这里kernel就完全接收并分析完成了uboot传过来的args。

 

简单的讲,uboot利用函数指针及传参规范,它将

 

l   R0: 0x0

l   R1: 机器号

l   R2: 参数地址

三个参数传递给内核。

 

其中,R2寄存器传递的是一个指针,这个指针指向一个TAG区域。

 

UBOOT和Linux内核之间正是通过这个扩展了的TAG区域来进行复杂参数的传递,如command line,文件系统信息等等,用户也可以扩展这个TAG来进行更多参数的传递。TAG区域的首地址,正是R2的值。

 

源文档 <http://blog.youkuaiyun.com/skyflying2012/article/details/35787971

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值