uboot向kernel的傳參機制——bootm與tags


http://blog.csdn.net/skyflying2012/article/details/35787971

 

最近閱讀代碼學習了uboot boot kernel的過程以及uboot如何傳參給kernel,記錄下來,與大家共享:

U-boot版本:2014.4

Kernel版本:3.4.55

 

一 uboot 如何啟動 kernel

1 do_bootm

uboot下使用bootm命令啟動內核鏡像文件uImage,uImage是在zImage頭添加了64字節的鏡像信息供uboot解析使用,具體這64字節頭的內容,我們在分析bootm命令的時候就會一一說到,那直接來看bootm命令。

在common/cmd_bootm.c中

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

  

數組boot_os是bootm最后階段啟動kernel時調用的函數數組,CONFIG_NEEDS_MANUAL_RELOC中的代碼含義是將boot_os函數都進行偏移(uboot啟動中會將整個code拷貝到靠近sdram頂端的位置執行),

但是boot_os函數在uboot relocate時已經都拷貝了,所以感覺沒必要在進行relocate。這個宏因此沒有定義,直接走下面。

新版uboot對於boot kernel實現了一個類似狀態機的機制,將整個過程分成很多個階段,uboot將每個階段稱為subcommand,

核心函數是do_bootm_states,需要執行哪個階段,就在do_bootm_states最后一個參數添加那個宏定義,如: BOOTM_STATE_START

do_bootm_subcommand是按照bootm參數來指定運行某一個階段,也就是某一個subcommand

對於正常的uImage,bootm加tftp的load地址就可以。

2 do_bootm_states

這樣會走到最后函數do_bootm_states,那就來看看核心函數do_bootm_states

static int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc,  
        char * const argv[], int states, bootm_headers_t *images,int boot_progress)  
{  
    boot_os_fn *boot_fn;  
    ulong iflag = 0;  
    int ret = 0, need_boot_fn;  
  
    images->state |= states;  
  
    /* 
     * Work through the states and see how far we get. We stop on 
     * any error. 
     */  
    if (states & BOOTM_STATE_START)  
        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

static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])  
{  
    memset((void *)&images, 0, sizeof(images));  
    images.verify = getenv_yesno("verify");  
  
    boot_start_lmb(&images);  
  
    bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");  
    images.state = BOOTM_STATE_START;  
  
    return 0;  
}  

  

獲取verify,bootstage_mark_name標志當前狀態為bootm start(bootstage_mark_name可以用於無串口調試,在其中實現LED控制)。

boot_start_lmb暫時還沒弄明白,以后再搞清楚。

最后修改images.state為bootm start。

bootm_start主要工作是清空images,標志當前狀態為bootm start。

 

4 第二階段:bootm_find_os

由bootm_start返回后,do_bootm傳了BOOTM_STATE_FINDOS,所以進入函數bootm_find_os

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

  

調用boot_get_kernel,函數較長,首先是獲取image的load地址,如果bootm有參數,就是img_addr,之后如下:

bootstage_mark(BOOTSTAGE_ID_CHECK_MAGIC);  
  
/* copy from dataflash if needed */  
img_addr = genimg_get_image(img_addr);  
  
/* check image type, for FIT images get FIT kernel node */  
*os_data = *os_len = 0;  
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。

 
switch (genimg_get_format(buf)) {  
case IMAGE_FORMAT_LEGACY:  
    printf("## Booting kernel from Legacy Image at %08lx ...\n",img_addr);  
    hdr = image_get_kernel(img_addr, images->verify);  
    if (!hdr)  
        return NULL;  
    bootstage_mark(BOOTSTAGE_ID_CHECK_IMAGETYPE);  
  
    /* get os_data and os_len */  
    switch (image_get_type(hdr)) {  
    case IH_TYPE_KERNEL:  
    case IH_TYPE_KERNEL_NOLOAD:  
        *os_data = image_get_data(hdr);  
        *os_len = image_get_data_size(hdr);  
        break;  
    case IH_TYPE_MULTI:  
        image_multi_getimg(hdr, 0, os_data, os_len);  
        break;  
 
  case IH_TYPE_STANDALONE:  
      *os_data = image_get_data(hdr);  
      *os_len = image_get_data_size(hdr);  
      break;  
  default:  
      printf("Wrong Image Type for %s command\n",cmdtp->name);  
      bootstage_error(BOOTSTAGE_ID_CHECK_IMAGETYPE);  
      return NULL;  
}  
  
/* 
 * copy image header to allow for image overwrites during 
 * kernel decompression. 
 */  
memmove(&images->legacy_hdr_os_copy, hdr,  sizeof(image_header_t));  
  
/* save pointer to image header */  
images->legacy_hdr_os = hdr;  
  
images->legacy_hdr_valid = 1;  
bootstage_mark(BOOTSTAGE_ID_DECOMP_IMAGE);  
break;  

  

 

首先來說明一下image header的格式,在代碼中由image_header_t代表,如下:

typedef struct image_header {  
    __be32      ih_magic;   /* Image Header Magic Number    */   
    __be32      ih_hcrc;    /* Image Header CRC Checksum    */  
    __be32      ih_time;    /* Image Creation Timestamp */  
    __be32      ih_size;    /* Image Data Size      */  
    __be32      ih_load;    /* Data  Load  Address      */  
    __be32      ih_ep;      /* Entry Point Address      */  
    __be32      ih_dcrc;    /* Image Data CRC Checksum  */  
    uint8_t     ih_os;      /* Operating System     */  
    uint8_t     ih_arch;    /* CPU architecture     */  
    uint8_t     ih_type;    /* Image Type           */  
    uint8_t     ih_comp;    /* Compression Type     */  
    uint8_t     ih_name[IH_NMLEN];  /* Image Name       */  
} image_header_t;  

  

genimg_get_format檢查img header的頭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,防止kernel image在解壓是覆蓋掉hdr數據,保存hdr指針到legacy_hdr_os中,置位legacy_hdr_valid。

從boot_get_kernel中返回到bootm_find_os,繼續往下:

switch (genimg_get_format(os_hdr)) {  
case IMAGE_FORMAT_LEGACY:  
    images.os.type = image_get_type(os_hdr);  
    images.os.comp = image_get_comp(os_hdr);  
    images.os.os = image_get_os(os_hdr);  
  
    images.os.end = image_get_image_end(os_hdr);  
    images.os.load = image_get_load(os_hdr);  
。。。

  

根據hdr獲取os的type,comp,os,end,load addr。

 
/* find kernel entry point */  
if (images.legacy_hdr_valid) {  
    images.ep = image_get_ep(&images.legacy_hdr_os_copy);  
} else {  
    puts("Could not find kernel entry point!\n");  
    return 1;  
}  
  
if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {  
    images.os.load = images.os.image_start;  
    images.ep += images.os.load;  
}  
  
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是直接編譯到kernel image中的。

回到do_bootm_states中接下來會調用bootm_load_os。

 

6 第四階段:bootm_load_os

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

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

  

 

load_buf是之前find_os是根據hdr獲取的load addr,image_buf是find_os獲取的image的開始地址(去掉64字節頭)。

之后則是根據hdr的comp類型來解壓拷貝image到load addr上。

這里就需要注意,kernel選項的壓縮格式必須在uboot下打開相應的解壓縮支持,或者就不進行壓縮

這里還有一點,load addr與image add是否可以重疊,看代碼感覺是可以重疊的,還需要實際測試一下。

回到do_bootm_states,接下來根據os從boot_os數組中獲取到了相應的os boot func,這里是linux,則是do_bootm_linux。后面代碼如下:

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

  

這時do_bootm最后的代碼,如果正常,boot kernel之后就不應該回來了。states中定義了BOOTM_STATE_OS_PREP(對於mips處理器會使用BOOTM_STATE_OS_CMDLINE),調用do_bootm_linux,如下:

int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)  
{  
    /* No need for those on ARM */  
    if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)  
        return -1;   
  
    if (flag & BOOTM_STATE_OS_PREP) {  
        boot_prep_linux(images);  
        return 0;  
    }     
  
    if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {  
        boot_jump_linux(images, flag);  
        return 0;  
    }     
  
    boot_prep_linux(images);  
    boot_jump_linux(images, flag);  
    return 0;  
}  

  

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

unsigned long machid = gd->bd->bi_arch_number;  
char *s;  
void (*kernel_entry)(int zero, int arch, uint params);  
unsigned long r2;  
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);  
  
kernel_entry = (void (*)(int, int, uint))images->ep;  
  
s = getenv("machid");  
if (s) {  
    strict_strtoul(s, 16, &machid);  
    printf("Using machid 0x%lx from environment\n", machid);  
}  
  
debug("## Transferring control to Linux (at address %08lx)" \  
    "...\n", (ulong) kernel_entry);  
bootstage_mark(BOOTSTAGE_ID_RUN_OS);  
announce_and_cleanup(fake);  
  
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)  
    r2 = (unsigned long)images->ft_addr;  
else  
    r2 = gd->bd->bi_boot_params;  
  
if (!fake)  
    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函數就可以了,如下:

static void boot_prep_linux(bootm_headers_t *images)  
{  
    char *commandline = getenv("bootargs");  
  
    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {  
#ifdef CONFIG_OF_LIBFDT  
        debug("using: FDT\n");  
        if (image_setup_linux(images)) {  
            printf("FDT creation failed! hanging...");  
            hang();  
        }  
#endif  
    } else if (BOOTM_ENABLE_TAGS) {  
        debug("using: ATAGS\n");  
        setup_start_tag(gd->bd);  
        if (BOOTM_ENABLE_SERIAL_TAG)  
            setup_serial_tag(¶ms);  
        if (BOOTM_ENABLE_CMDLINE_TAG)  
            setup_commandline_tag(gd->bd, commandline);  
        if (BOOTM_ENABLE_REVISION_TAG)  
            setup_revision_tag(¶ms);  
        if (BOOTM_ENABLE_MEMORY_TAGS)  
            setup_memory_tags(gd->bd);  
        if (BOOTM_ENABLE_INITRD_TAG) {  
            if (images->rd_start && images->rd_end) {  
                setup_initrd_tag(gd->bd, images->rd_start,images->rd_end);  
            }  
        }  
        setup_board_tags(¶ms);  
        setup_end_tag(gd->bd);  
    } else {  
        printf("FDT and ATAGS support not compiled in - hanging\n");  
        hang();  
    }  
    do_nonsec_virt_switch();  
}  

  

首先獲取出環境變量bootargs,這就是要傳遞給kernel的參數。
在配置文件中定義了CONFIG_CMDLINE_TAG以及CONFIG_SETUP_MEMORY_TAGS,根據arch/arm/include/asm/bootm.h,則會定義BOOTM_ENABLE_TAGS,首先調用setup_start_tag,如下:

 static void setup_start_tag (bd_t *bd)  
{         
    params = (struct tag *)bd->bi_boot_params;  
          
    params->hdr.tag = ATAG_CORE;  
    params->hdr.size = tag_size (tag_core);  
              
    params->u.core.flags = 0;  
    params->u.core.pagesize = 0;  
    params->u.core.rootdev = 0;  
              
    params = tag_next (params);  
}     

params是一個全局靜態變量用來存儲要傳給kernel的參數,這里bd->bi_boot_params的值賦給params,因此bi_boot_params需要進行初始化,從而將params放在一個合理的內存區域。
這里params為struct tag的結構,如下:

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;  
  
        /* 
         * Acorn specific 
         */  
        struct tag_acorn    acorn;  
  
        /* 
         * DC21285 specific 
         */  
        struct tag_memclk   memclk;  
    } u;  
};  

  

tag包括hdr和各種類型的tag_*,hdr來標志當前的tag是哪種類型的tag。
setup_start_tag是初始化了第一個tag,是tag_core類型的tag。最后調用tag_next跳到第一個tag末尾,為下一個tag做准備。

回到boot_prep_linux,接下來調用setup_commandline_tag,如下:

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

  

該函數設置第二個tag的hdr.tag為ATAG_CMDLINE,然后拷貝cmdline到tags的cmdline結構體中,跳到下一個tag。

回到boot_prep_linux,調用setup_memory_tag,如下:

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);  
    }     
} 

  

過程類似,將第三個tag設為ATAG_MEM,將mem的start,size保存在此處,如果有多片ram(CONFIG_NR_DRAM_BANKS > 1),則將下一個tag保存下一片ram的信息,依次類推。

回到boot_prep_linux中,調用setup_board_tags,這個函數是__weak屬性,我們可以在自己的板級文件中去實現來保存跟板子相關的參數,如果沒有實現,則是空函數。

最后調用setup_end_tags,如下:

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

  

最后將最末尾的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,中可以看到如下一段匯編:

/*   
 * r1 = machine no, r2 = atags or dtb, 
 * r8 = phys_offset, r9 = cpuid, r10 = procinfo 
 */  
bl  __vet_atags  

  

可以看出kernel剛啟動會調用__vet_atags來處理uboot傳來的參數,如下:

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

  

主要是對tag進行了一個簡單的校驗,查看tag頭4個字節(tag_core的size)和第二個4字節(tag_core的type)。

之后對參數的真正分析處理是在start_kernel的setup_arch中,在arch/arm/kernel/setup.c中,如下:

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

 

關鍵函數是setup_machine_tags,如下:

static struct machine_desc * __init setup_machine_tags(unsigned int nr)  
{  
    struct tag *tags = (struct tag *)&init_tags;  
    struct machine_desc *mdesc = NULL, *p;  
    char *from = default_command_line;  
。。。。  
    if (__atags_pointer)  
        tags = phys_to_virt(__atags_pointer);  
    else if (mdesc->atag_offset)  
        tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);  
  
。。。。。  
    if (tags->hdr.tag == ATAG_CORE) {  
        if (meminfo.nr_banks != 0)  
            squash_mem_tags(tags);  
        save_atags(tags);  
        parse_tags(tags);  
    }  
  
    /* parse_early_param needs a boot_command_line */  
    strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);  
。。。  
}  

 

首先回去獲取tags的首地址,如果收個tag是ATAG_CORE類型,則會調用save_atags拷貝一份tags,最后調用parse_tags來分析這個tag list,如下:

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

 

遍歷tags list,找到在tagstable中匹配的處理函數(hdr.tag一致),來處理響應的tag。

這個tagtable的處理函數是在調用__tagtable來注冊的,如下:

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

 

看這個對cmdline類型的tag的處理,就是將tag中的cmdline拷貝到default_command_line中。還有其他如mem類型的參數也會注冊這個處理函數,來匹配處理響應的tag。這里就先以cmdline的tag為例。

這樣遍歷並處理完tags list之后回到setup_machine_tags,將from(即default_command_line)中的cmdline拷貝到boot_command_line,

最后返回到setup_arch中,

/* populate cmd_line too for later use, preserving boot_command_line */  
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);  
*cmdline_p = cmd_line;  
  
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的值。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM