u-boot给内核传参说明

u-boot给内核传参说明


  众所周知,u-boot传递给内核三个参数,即通过r0、r1、r2寄存器传递给内核。并且,r0的值暂时没有用到,缺省放0。
  那么本篇文章主要来讲一下r1和r2的值

寄存器含义
R1机器号,标识计算机系统的型号
R2ATAGS or dtb

  说明:关于内核从head.S汇编执行到start_kernel部分这里就不介绍了。最终执行到start_kernel时。r1被赋值给__machine_arch_type全局变量,r2被赋值给__atags_pointer全局变量。

  内核提供了一个重要的结构体struct machine_desc ,这个结构体在内核移植中起到相当重要的作用,内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。
  内核编译时维护了一份machine_desc的全局数据结构,用于和uboot传参匹配,做一些芯片相关的初始化工作。

machine_desc数据结构

struct machine_desc {
	unsigned int		nr;		/* architecture number	*/
	const char		*name;		/* architecture name	*/
	unsigned long		atag_offset;	/* tagged list (relative) */
	const char *const 	*dt_compat;	/* array of device tree
						 * 'compatible' strings	*/

	unsigned int		nr_irqs;	/* number of IRQs */

#ifdef CONFIG_ZONE_DMA
	phys_addr_t		dma_zone_size;	/* size of DMA-able area */
#endif

	unsigned int		video_start;	/* start of video RAM	*/
	unsigned int		video_end;	/* end of video RAM	*/

	unsigned char		reserve_lp0 :1;	/* never has lp0	*/
	unsigned char		reserve_lp1 :1;	/* never has lp1	*/
	unsigned char		reserve_lp2 :1;	/* never has lp2	*/
	enum reboot_mode	reboot_mode;	/* default restart mode	*/
	unsigned		l2c_aux_val;	/* L2 cache aux value	*/
	unsigned		l2c_aux_mask;	/* L2 cache aux mask	*/
	void			(*l2c_write_sec)(unsigned long, unsigned);
	const struct smp_operations	*smp;	/* SMP operations	*/
	bool			(*smp_init)(void);
	void			(*fixup)(struct tag *, char **);
	void			(*dt_fixup)(void);
	long long		(*pv_fixup)(void);
	void			(*reserve)(void);/* reserve mem blocks	*/
	void			(*map_io)(void);/* IO mapping function	*/
	void			(*init_early)(void);
	void			(*init_irq)(void);
	void			(*init_time)(void);
	void			(*init_machine)(void);
	void			(*init_late)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
	void			(*handle_irq)(struct pt_regs *);
#endif
	void			(*restart)(enum reboot_mode, const char *);
}

nr用于和uboot传参的machine id进行匹配。
dt_compat字符串数组,用于与使用设备树的uboot传参,进行比对字符串。
剩下一些回调函数,基本名字翻译过来就是相关的功能。可根据芯片实际情况选择实现or不实现。

  struct machine_desc通过以下两个宏来定义

/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#define MACHINE_START(_type,_name)			\
static const struct machine_desc __mach_desc_##_type	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_##_type,		\
	.name		= _name,

#define MACHINE_END				\
};

#define DT_MACHINE_START(_name, _namestr)		\
static const struct machine_desc __mach_desc_##_name	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= ~0,				\
	.name		= _namestr,

  顾名思义,上面的是传统的machine_desc定义方式,需要定义machine id。下面的是dtb的定义方式,不需要严格定义machine_id。直接置位0xFFFFFFFF。设备树通过上面讲的“dt_compat”字符串数组匹配。

  machine_desc数据结构,存储在内核特定的section中。可见宏定义中的__attribute__((__section__(".arch.info.init")))。内核编译后可以通过System.map查看。全志平台示例:

c1035e1c T __arch_info_begin
c1035e1c t __mach_desc_GENERIC_DT.31656
c1035e84 t __mach_desc_SUN9I_DT
c1035eec t __mach_desc_SUN8I_DT
c1035f54 t __mach_desc_SUN7I_DT
c1035fbc t __mach_desc_SUN6I_DT
c1036024 t __mach_desc_SUNXI_DT
c103608c T __arch_info_end

  因为数据结构大小固定,所以内核可以通过__arch_info_begin地址,加上数据结构大小偏移,逐一访问这些结构体。

代码初始化

start_kernel->setup_arch
  setup_arch与arch体系有关,这里以arm32代码为例进行说明。

/*
 * __atags_pointer: 设备树orATAGS
 * __machine_arch_type:machine id
 */
void __init setup_arch(char **cmdline_p)
{
	const struct machine_desc *mdesc;

	setup_processor();
	mdesc = setup_machine_fdt(__atags_pointer);
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
	machine_desc = mdesc;
	machine_name = mdesc->name;
	dump_stack_set_arch_desc("%s", mdesc->name);
	......

  __atags_pointer可能是dtb,也可能是ATAGS,所以需要逐一判断,优先判断dtb情况。(这个变量名字应该是历史遗留吧,最开始只有ATAGS)。
  先看扫描设备树setup_machine_fdt函数

const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
	const struct machine_desc *mdesc, *mdesc_best = NULL;

#if defined(CONFIG_ARCH_MULTIPLATFORM) || defined(CONFIG_ARM_SINGLE_ARMV7M)
	DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
		.l2c_aux_val = 0x0,
		.l2c_aux_mask = ~0x0,
	MACHINE_END

	mdesc_best = &__mach_desc_GENERIC_DT;
#endif

	if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))          (1)
		return NULL;

	mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);      (2)

	if (!mdesc) {                                                          (3)
		const char *prop;
		int size;
		unsigned long dt_root;

		early_print("\nError: unrecognized/unsupported "
			    "device tree compatible list:\n[ ");

		dt_root = of_get_flat_dt_root();
		prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
		while (size > 0) {
			early_print("'%s' ", prop);
			size -= strlen(prop) + 1;
			prop += strlen(prop) + 1;
		}
		early_print("]\n\n");

		dump_machine_table(); /* does not return */
	}

	/* We really don't want to do this, but sometimes firmware provides buggy data */
	if (mdesc->dt_fixup)                            (4)
		mdesc->dt_fixup();

	early_init_dt_scan_nodes();                     (5)

	/* Change machine number to match the mdesc we're using */
	__machine_arch_type = mdesc->nr;

	return mdesc;
}

(1)判断指针是否为空,非空情况下再判断是否为dtb数据结构,即check dtb header。如果是ATAGS情况这里就应该直接返回了。如果是dtb,这里会将全局变量initial_boot_params指向dtb所在位置。
(2)of_flat_dt_match_machine作用是遍历所有machine_desc数据结构,并拿到每个数据结构内的dt_compat字符串数组,与dtb root节点的compatible进行匹配。匹配上即可拿到对应的machine_desc
(3)如果遍历了所有machine_desc,没有找到合适的,dump所有machine_desc
(4)如果定义了机器的dt_fixup回调,则执行。
(5)扫描设备树,获取 1)commandline 2)root节点的一些参数 3)memory参数(内存大小,起始物理地址等)

  上文(1)(3)情况退出时,就需要继续判断ATAGS情况了,即setup_machine_tags函数

const struct machine_desc * __init
setup_machine_tags(phys_addr_t __atags_pointer, unsigned int machine_nr)
{
	struct tag *tags = (struct tag *)&default_tags;
	const struct machine_desc *mdesc = NULL, *p;
	char *from = default_command_line;

	default_tags.mem.start = PHYS_OFFSET;

	/*
	 * locate machine in the list of supported machines.
	 */
	for_each_machine_desc(p)                           (1)
		if (machine_nr == p->nr) {
			pr_info("Machine: %s\n", p->name);
			mdesc = p;
			break;
		}

	if (!mdesc) {                                      (2)
		early_print("\nError: unrecognized/unsupported machine ID"
			    " (r1 = 0x%08x).\n\n", machine_nr);
		dump_machine_table(); /* does not return */
	}

	if (__atags_pointer)                               (3)
		tags = phys_to_virt(__atags_pointer);
	else if (mdesc->atag_offset)                       (4)
		tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);

#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
	/*
	 * If we have the old style parameters, convert them to
	 * a tag list.
	 */
	if (tags->hdr.tag != ATAG_CORE)
		convert_to_tag_list(tags);
#endif
	if (tags->hdr.tag != ATAG_CORE) {
		early_print("Warning: Neither atags nor dtb found\n");
		tags = (struct tag *)&default_tags;
	}

	if (mdesc->fixup)
		mdesc->fixup(tags, &from);                      (5)

	if (tags->hdr.tag == ATAG_CORE) {
		if (memblock_phys_mem_size())
			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);  (6)

	return mdesc;
}

(1)遍历machine_desc,与传参的machine_nr进行匹配。(使用ATAGS的内核,machine_desc必须定义nr,且必须与uboot传递的保持一致)
(2)找不到dump所有描述符
(3)传参了ATAGS物理地址,则转换为虚拟地址
(4)没传地址,且机器描述符定义了ATAGS偏移地址情况下,使用改地址的ATAGS。当3、4情况都不满足时,使用default_tags。
(5)通过fixup回调做machine初始化操作,并且填充command line。如果没填充,则使用default_command_line。
注:剩下的一些操作都是ATAG相关的了。

小结

  综上,通过dtb传参的方式,可以不需要machine id。而通过ATAGS传参的方式,则需要machine id来匹配machine_desc
  在引入dts的内核版本后,基本都是用dtb得到方式传参了。当然也有一些芯片厂家,为了兼容老的sdk,还是有使用atags的方式。这里就引申出了一个问题,同时需要穿atags和dtb的情况。
  讲一下我了解到的几种方式
1)内核menuconfig支持,dtb append在内核后面,也就是在内核后面拼接一个dtb。然后uboot传参时,r2照样传递atags,dtb则从内核镜像后面去获取。存在内核和dtb绑定了,不能灵活兼容不同dtb的问题,不过这个问题可以在u-boot中规避,jump前根据板级信息,选择不同的dtb拼接在内核后面即可。海思安防芯片sdk就是通过这种方式同时传递atags和dtb的。

2)atags增加dtb的tag。将dtb作为atags的一部分,传递给内核,内核解析atags类型,获取dtb内容。

3)八仙过海,各显神通,瞎jb魔改就完事了。

  完!

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值