• cmdline(二):uboot cmdline怎么传?&&cmdline kernel怎么用?


    前面我们知道了cmdline是什么,已经在哪里添加cmdline?现在我们来看看在哪里传输cmdline,以及传输收到后怎么用?

    参考内容来自前辈,感激:
    https://blog.csdn.net/weixin_42031299/article/details/121239507
    https://blog.csdn.net/weixin_42135087/article/details/107957684

    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;
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    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;
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    当你想实现相应的参数在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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    这里调用了:

    • 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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    __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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    __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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在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);
    ......
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
     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;
     }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
     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);
     }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在start_kernel()打印了cmdline.

    asmlinkage __visible void __init start_kernel(void)
    {
    …
    pr_notice(“Kernel command line: %s\n”, boot_command_line);
    …
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    了解zabbix
    (附源码)springboot码头作业管理系统 毕业设计 341654
    win10 OpenVINO2022版
    软件项目尾期,客户提新需求怎么办?
    基于springboot的小说阅读网站设计与实现【附源码】
    如何清理 mac 磁盘?快速清理小妙招在这里
    API接口是什么?有哪些免费的API接口?
    选硬币该用动态规划
    C++ Reference: Standard C++ Library reference: C Library: cwchar: wprintf
    Harmony系统更改手机IP
  • 原文地址:https://blog.csdn.net/weixin_45264425/article/details/128063416