最近閱讀代碼學習了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的值。