1、uboot以tag方式给内核传参–怎么传?
我们知道cmdline是由bootloader传给kernel的。
怎么传输的?
uboot是通过在启动内核时,向内核传递tag参数,其中就包括cmdline
1、tag方式传参
- (1)struct tag,tag是一个数据结构,在uboot和linux kernel中都有定义tag数据机构,而且定义是一样的。
- (2)tag_header和tag_xxx。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理。
- (3)tag_start与tag_end。kernel接收到的传参是若干个tag构成的,这些tag由ATAG_CORE类型的tag起始,到ATAG_NONE类型的tag结束。
- (4)tag传参的方式是由linux kernel发明的,kernel定义了这种向自身传参的方式,uboot只是实现了这种传参方式从而可以支持给kernel传参。(接口的依赖倒置)
2、内核如何接收tag参数
启动内核的代码:theKernel (0, machid, bd->bi_boot_params);
其中bd->bi_boot_params就是所有tag结构体所在的首地址,这个地址是保存在全局变量gd->bd中的,
在uboot启动的前期会指定内存地址用于存放tag结构体,然后在启动内核的时候传给内核,
内核拿到地址就会从该地址去遍历tag结构体,内核会判断tag的类型,如果是ATAG_CORE类型的tag则是起始的tag,
如果是ATAG_NONE则是最后一个tag结构体,不用再往后遍历。
3、tag结构体
struct tag_header { u32 size; //结构体的大小 u32 tag; //结构体的类型 }; struct tag { struct tag_header hdr; union { //此枚举体包含了uboot传给内核参数的所有类型 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; struct tag_mtdpart mtdpart_info; } u; };
4、构建tag结构体
/* The list must start with an ATAG_CORE node */ #define ATAG_CORE 0x54410001 /* The list ends with an ATAG_NONE node. */ #define ATAG_NONE 0x00000000 #define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2) #define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size)) static struct tag *params; static void setup_start_tag (bd_t *bd) { params = (struct tag *) bd->bi_boot_params;//bd->bi_boot_params是专门用于保存tag结构体的内存首地址 params->hdr.tag = ATAG_CORE; //ATAG_CORE类型是tag结构体的开始 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->hdr.size个字节,让params指向下一个可用的内存地址 } `````````中间省略掉其他类型tag结构体的构建 static void setup_end_tag (bd_t *bd) { params->hdr.tag = ATAG_NONE; params->hdr.size = 0; }
当你想实现相应的参数在uboot传输到kernel,比如uboot在设备树解析后,把里面的参数传输到kernel。那么你要在uboot和kernel都相应的添加,你参考已经存在的tag结构体添加解析,uboot中传递,内核中解析。(但是推荐设备树传递,比较方便。)
因此参数的传递其实也是通过地址的方式:uboot把内核复制到SDRAM之后,需要跳转到内核的入口函数执行。在跳转之前,还要给内核传递启动参数。传递方式是uboot把启动参数按一定的格式放在指定的地址(位于SDRAM),启动内核之后,内核再去这个地址上读取启动参数。
在uboot中,将cmdline统一放置在FDT中。FDT,flatted device tree,扁平设备树。熟悉linux的人对这个概念应该不陌生。
简单理解为将部分设备信息结构存放到device tree文件中。
2、Kernel怎么解析这个传过来的参数呢?
(1)、跳转linux kernel之前-准备cmdline
在跳转linux kernel之前(如uboot中),将cmdline数据放到了FDT中,然后将FDT的地址写入到了X0中。然后再跳转linux kernel.
请看kernel-4.14/Documentation/arm64/booting.txt
Before jumping into the kernel, the following conditions must be met: - Quiesce all DMA capable devices so that memory does not get corrupted by bogus network packets or disk data. This will save you many hours of debug. - Primary CPU general-purpose register settings x0 = physical address of device tree blob (dtb) in system RAM. x1 = 0 (reserved for future use) x2 = 0 (reserved for future use) x3 = 0 (reserved for future use)
(2)、kernel启动-解析cmdline
linux kernel从stext开始启动,整个流程大概就是读取X0(FDT地址)保存到X21中,又将X21保存到__fdt_pointer全局变量中
然后再将__fdt_pointer解析处cmdline数据到boot_command_line全局变量中。
/* * The following callee saved general purpose registers are used on the * primary lowlevel boot path: * * Register Scope Purpose * x21 stext() .. start_kernel() FDT pointer passed at boot in x0 * x23 stext() .. start_kernel() physical misalignment/KASLR offset * x28 __create_page_tables() callee preserved temp register * x19/x20 __primary_switch() callee preserved temp registers */ ENTRY(stext) bl preserve_boot_args bl el2_setup // Drop to EL1, w0=cpu_boot_mode adrp x23, __PHYS_OFFSET and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0 bl set_cpu_boot_mode_flag bl __create_page_tables /* * The following calls CPU setup code, see arch/arm64/mm/proc.S for * details. * On return, the CPU will be ready for the MMU to be turned on and * the TCR will have been set. */ bl __cpu_setup // initialise processor b __primary_switch ENDPROC(stext)
这里调用了:
- preserve_boot_args
- __primary_switch
在preserve_boot_args将X0(fdt地址)暂时先保存到了X21中
preserve_boot_args: mov x21, x0 // x21=FDT adr_l x0, boot_args // record the contents of stp x21, x1, [x0] // x0 .. x3 at kernel entry stp x2, x3, [x0, #16] dmb sy // needed before dc ivac with // MMU off mov x1, #0x20 // 4 x 8 bytes b __inval_dcache_area // tail call ENDPROC(preserve_boot_args)
__primary_switch调用了__primary_switched __primary_switch: #ifdef CONFIG_RANDOMIZE_BASE mov x19, x0 // preserve new SCTLR_EL1 value mrs x20, sctlr_el1 // preserve old SCTLR_EL1 value #endif bl __enable_mmu #ifdef CONFIG_RELOCATABLE bl __relocate_kernel #ifdef CONFIG_RANDOMIZE_BASE ldr x8, =__primary_switched adrp x0, __PHYS_OFFSET blr x8
__primary_switched将X21(fdt地址)保存到了__fdt_pointer全局变量中
__primary_switched: adrp x4, init_thread_union add sp, x4, #THREAD_SIZE adr_l x5, init_task msr sp_el0, x5 // Save thread_info adr_l x8, vectors // load VBAR_EL1 with virtual msr vbar_el1, x8 // vector table address isb stp xzr, x30, [sp, #-16]! mov x29, sp str_l x21, __fdt_pointer, x5 // Save FDT pointer ldr_l x4, kimage_vaddr // Save the offset between sub x4, x4, x0 // the kernel virtual and str_l x4, kimage_voffset, x5 // physical mappings // Clear BSS adr_l x0, __bss_start mov x1, xzr adr_l x2, __bss_stop sub x2, x2, x0 bl __pi_memset dsb ishst // Make zero page visible to PTW
在setup_arch()的时候,调用setup_machine_fdt将fdt解析到了boot_command_line全局变量中
void __init setup_arch(char **cmdline_p) { pr_info("Boot CPU: AArch64 Processor [%08x]\n", read_cpuid_id()); ...... *cmdline_p = boot_command_line; ...... setup_machine_fdt(__fdt_pointer); ...... }
setup_machine_fdt()—>early_init_dt_scan()—>early_init_dt_scan_nodes()
通过调用将fdt解析到了boot_command_line中,of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line)
static void __init setup_machine_fdt(phys_addr_t dt_phys) { void *dt_virt = fixmap_remap_fdt(dt_phys); const char *name; if (!dt_virt || !early_init_dt_scan(dt_virt)) { pr_crit("\n" "Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n" "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n" "\nPlease check your bootloader.", &dt_phys, dt_virt); while (true) cpu_relax(); } name = of_flat_dt_get_machine_name(); if (!name) return; /* backward-compatibility for third-party applications */ machine_desc_set(name); pr_info("Machine model: %s\n", name); dump_stack_set_arch_desc("%s (DT)", name); }
bool __init early_init_dt_scan(void *params) { bool status; status = early_init_dt_verify(params); if (!status) return false; early_init_dt_scan_nodes(); return true; }
void __init early_init_dt_scan_nodes(void) { /* Retrieve various information from the /chosen node */ of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); /* Initialize {size,address}-cells info */ of_scan_flat_dt(early_init_dt_scan_root, NULL); /* Setup memory, calling early_init_dt_add_memory_arch */ of_scan_flat_dt(early_init_dt_scan_memory, NULL); }
在start_kernel()打印了cmdline.
asmlinkage __visible void __init start_kernel(void) { … pr_notice(“Kernel command line: %s\n”, boot_command_line); … }