dtb解析过程及platform_device 创建过程

  • Post author:
  • Post category:其他




内核解析dtb 文件,并构建树状devide_node 的过程

本节将探究以下问题:

  1. dtb被编译好传给内核之后,内核是如何解析的?解析的结果是什么?
  2. device_node 是在哪里被alloc 的?被填充了哪些内容?
/* 在 drivers/of/base.c 中,创建了一个全局的指针*/
struct device_node *of_root;
EXPORT_SYMBOL(of_root);

start_kernel()
	/*arch/arm/kernel/setup.c*/
	setup_arch(char **cmdline_p)
		/*
		 * 这里的__atags_pointer 是定义在arch/arm/kernel/head-common.S 中:
		 * .long   processor_id            @ r0
		 * .long   __machine_arch_type     @ r1
		 * .long   __atags_pointer         @ r2
		 * 保存了uboot传给内核的第三个参数,也就是启动参数所在物理地址
		 */
		mdesc = setup_machine_fdt(__atags_pointer);
			dt_phys = __atags_pointer;
			early_init_dt_verify(phys_to_virt(dt_phys));
				params = phys_to_virt(dt_phys);
				/*
				 * 重点!从这里可以看到initial_boot_params 就是启动参数,也就是设备树所在地址。
				 * 后边这个参数都是直接拿来就用
				 */
				initial_boot_params = params;
		unflatten_device_tree()
			/*
			 * drivers/of/fdt.c 看起来下边这个函数给of_root 赋了值
			 * initial_boot_params:dtb 所在地址
			 * of_root:解析完的设备树根节点就保存在这里
			 * early_init_dt_alloc_memory_arch: An allocator that provides a virtual address to memory
			 * 		for the resulting tree
			 * 		看起来是个内存分配的函数。可能是因为此时kmalloc还不能用?
			 */ 
			__unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false);
				先将设备树扫描一遍,得到需要分配的内存大小,然后分配一块内存mem
				/*下边这里真正开始unflattern*/
				unflatten_dt_nodes(initial_boot_params, mem, NULL, &of_root);
						for (offset = 0; 
								offset >= 0 && depth >= initial_depth;
								offset = fdt_next_node(blob, offset, &depth)) {
							/*
							 * 核心在于下边这个函数populate_node,会从blob+offset 的位置解析一个dts节点出来,创建相应的
							 * device_node。然后填充device_node,并赋值给 nps[depth+1]。
							 * 其中,新建的device_node *np:
							 * 		np->parent = dad;
							 * 		np->sibling = dad->child;
							 * 		dad->child = np;
							 * 即,当前创建节点(np)的大哥(sibling)指向父亲(dad)上次刚出生的孩子(dad->child),这样就把所
							 * 有的device_node 串了起来。其中,每个节点能直接找到的孩子(np->child)都是最晚出生(创建)的那
							 * 个老小,其他的孩子必须通过这个老小的sibling指针链式查找。为了直观,画了一个device_node 
							 * 层级关系图。
							 * 
							 * 值得注意的是,np的name和device_type直接是结构体device_node的属性,而compatible和其他所有
							 * 的属性是作为 propertie保存在device_node中的。np->name 对应dts中的name属性(而非
							 * compatible)。但是通常dts节点并不存在name属性,所以np->name 一般为空。device_type 也是一样
							 */
							if (!populate_node(blob, offset, &mem, nps[depth],
									   &nps[depth+1], dryrun))
								return mem - base;
			
							if (!dryrun && nodepp && !*nodepp)
								*nodepp = nps[depth+1];
							if (!dryrun && !root)
								root = nps[depth+1];
						}
				

		
			/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
			of_alias_scan(early_init_dt_alloc_memory_arch);
		
			unittest_unflatten_overlay_base();




device_node 的层级关系详解

这里把devide_node 的层级关系单独拿出来研究一下,前面已经提到“每个节点能直接找到的孩子(np->child)都是最晚出生(创建)的那个老小,其他的孩子必须通过这个老小的sibling指针链式查找。为了验证这个猜测,我们可以看一下of_platform_populate 函数中是如何遍历所有device_node的

/* root就是device_node 根节点*/
of_platform_populate(root, of_default_bus_match_table, lookup, parent);
	for_each_child_of_node(root, child)
		for (child = of_get_next_child(parent, NULL); child != NULL; child = of_get_next_child(parent, child))
			/*
			 * 下边这个函数是重点,这是个递归函数,其中还存在一个遍历每个child的过程。
			 * 遍历个过程就是先父辈,再子辈。同辈内先年纪从小到遍历。其实就是按照dts 从上到下的顺序遍历的。
			 * 在drivers/of/platform.c 中定义DEBUG宏就能看到 “create child: %pOF” 的打印。
			 */
			of_platform_bus_create(child, matches, lookup, parent, true);
				of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
				    for_each_child_of_node(bus, child)
				    	pr_debug("   create child: %pOF\n", child);
				        of_platform_bus_create(child, matches, lookup, &dev->dev, strict);				


/*另起一行看一下of_get_next_child 的实现*/
of_get_next_child(parent, child)
	node = parent, prev = child;
	__of_get_next_child(node, prev)
		/*
		 * 核心就是下边这一行了
		 * 	1. node == np_dad,prev = NULL时,next=node->child,也就是拿到了图中的np3
		 *  2. 再次调用时,prev == np3,,next=np3->sibling,也就是拿到np2
		 *  3. 重复2,到prev == np0时,next=NULL
		 */
		next = prev ? prev->sibling : node->child;
		for (; next; next = next->sibling)
			if (of_node_get(next))
				break;
		of_node_put(prev);
		return next;	

在这里插入图片描述

PS: 上边的pr_debug(” create child: %pOF\n”, child); 打印把我看懵了,本以为打出来的是个地址,但实际上打出来这个:

[ 0.199217] OF: create child: /soc/aips-bus@2000000

[ 0.199580] OF: create child: /soc/aips-bus@2000000/spba-bus@2000000

查了下文档,才知道%p有这么多用法

在这里插入图片描述



platform_device 创建过程

在自己写的platform driver驱动中,例如下边这个

static int spi_imx_probe(struct platform_device *pdev){...}

static struct platform_driver spi_imx_driver = {
	.probe = spi_imx_probe,
	...
};

platform_driver的probe 函数是有一个入参struct platform_device *pdev的,这玩意儿哪来的?谁alloc出来的?其中填充了什么内容?本节将一探究竟。

start_kernel     // init/main.c
    rest_init();
        kernel_thread(kernel_init, NULL, CLONE_FS);
			kernel_init
				kernel_init_freeable();
					do_basic_setup();
						do_initcalls();
							for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
								do_initcall_level(level);  // 比如 do_initcall_level(3)
								   for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
										do_one_initcall(initcall_from_entry(fn));//arch_initcall_sync ,即段“.init”声明的函数

/* drivers/of/platform.c */
arch_initcall_sync(of_platform_default_populate_init);
of_platform_default_populate_init(void)
	/* Populate everything else. */
	of_platform_default_populate(NULL, NULL, NULL);
		/*
		这里关注of_default_bus_match_table的定义,会发现.compatible = "simple-bus"与dts中的soc的compatible一致。
		const struct of_device_id of_default_bus_match_table[] = {
			{ .compatible = "simple-bus", },
			{ .compatible = "simple-mfd", },
			{ .compatible = "isa", },
		#ifdef CONFIG_ARM_AMBA
			{ .compatible = "arm,amba-bus", },
		#endif /* CONFIG_ARM_AMBA */
			{} /* Empty terminated list */
		};
		*/
		of_platform_populate(root, of_default_bus_match_table, lookup, parent);
				for_each_child_of_node(root, child) {
					rc = of_platform_bus_create(child, matches, lookup, parent, true);
				}

其中,of_platform_bus_create 是一个递归函数,会遍历所有child node,逐一创建dev。创建时,会忽略掉几类node,比如没有compatible 属性的等。最终调用of_platform_device_create_pdata 函数来创建节点

/* drivers/of/platform.c */
/**
 * of_platform_device_create_pdata - Alloc, initialize and register an of_device
 * @np: pointer to node to create device for
 * @bus_id: name to assign device
 * @platform_data: pointer to populate platform_data pointer with
 * @parent: Linux device model parent device.
 *
 * Returns pointer to created platform device, or NULL if a device was not
 * registered.  Unavailable devices will not get registered.
 */
static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id,
					void *platform_data, struct device *parent)
	struct platform_device *dev;
	/* 这里分配pdev 的时候,就已经把pdev->dev 的of_node, res, parent等参数配置好了 */
	dev = of_device_alloc(np, bus_id, parent);
	dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
	if (!dev->dev.dma_mask)
		dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
	/* 挂载到platform_bus 上,等待与platform_driver 相匹配 */
	dev->dev.bus = &platform_bus_type;
	/* 这里的platform_data 貌似是空的*/
	dev->dev.platform_data = platform_data;
	/* 貌似是这中中断:Message Signaled Interrupts */
	of_msi_configure(&dev->dev, dev->dev.of_node);
	/* 这里将创建好的dev 添加到内核 */
	of_device_add(dev);

还是有必要单独看一下of_device_alloc 和of_device_add 这两个函数

/*
这个函数的主要工作有:
1. 分配一个空的pdev,并将一些资源赋值给它
	a. 为每个寄存器地址和中断分配res
	b. of_node, parent 赋值
	c. 利用bus id 命名,类似spi1这样?
*/
struct platform_device *of_device_alloc(struct device_node *np, const char *bus_id, struct device *parent)
	struct platform_device *dev;
	int rc, i, num_reg = 0, num_irq;
	struct resource *res, temp_res;

	dev = platform_device_alloc("", PLATFORM_DEVID_NONE);

	/* count the io and irq resources */
	while (of_address_to_resource(np, num_reg, &temp_res) == 0)
		num_reg++;
	
	/* 这里对设备节点的每一个irq 资源都创建了一个irq_desc*/
	num_irq = of_irq_count(np);
	/* Populate the resource table */
	if (num_irq || num_reg) {
		res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);

		dev->num_resources = num_reg + num_irq;
		dev->resource = res;
		for (i = 0; i < num_reg; i++, res++)
			rc = of_address_to_resource(np, i, res);
		if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
			pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
				 np);
	}

	dev->dev.of_node = of_node_get(np);
	dev->dev.fwnode = &np->fwnode;
	dev->dev.parent = parent ? : &platform_bus;

	if (bus_id)
		dev_set_name(&dev->dev, "%s", bus_id);
	else
		of_device_make_bus_id(&dev->dev);

注:这里的 of_irq_to_resource_table 过程是 request_irq 中的irq_to_desc 的前提条件。具体可以看

这篇blog

的中断注册过程小节。



版权声明:本文为weixin_43512663原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。