Linux 设备树的加载与匹配

之前学习了platform设备与总线是如何匹配的,但是在读某一驱动程序中,该设备由dts文件描述,设备的匹配与platform设有所不同,因此记录下来。

1. 什么是设备树

在内核源码中存在大量对板级细节信息描述的代码,但是对于内核而言,这些代码对于内核毫无意义。 ARM内核版本3.x引入了Flattened Device Tree(FDT),这是一种描述硬件资源的数据结构,通过BootLoader将硬件资源传给内核,使得内核和硬件资源描述相对独立。采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余码。
本质上,Device Tree改变了原来用hardcode方式将HW 配置信息嵌入到内核代码的方法,改用bootloader传递一个DB的形式。
设备树是一种描述硬件的数据结构,采用设备树后,硬件细节可以直接通过设备树传递给Linux,不需要在内核中进行大量的冗余编码。
Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):

  • CPU的数量和类别
  • 内存基地址和大小
  • 总线和桥
  • 外设连接
  • 中断控制器和中断使用情况
  • GPIO控制器和GPIO使用情况
  • Clock控制器和Clock使用情况

它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

DTS(device tree source)

.dts文件是一种对Device Tree的ASCII文本描述,一个dts文件对应一个ARM架构的machine。但是一个SOC板可能对应多个产品。这些产品的dts文件会存在大量冗余。为了简化,Device Tree将这些冗余提炼为.dtsi文件,dtsi文件相当于C语言的头文件,dts文件需要include引入dtsi文件。

DTC(编译工具)

DTC为编译工具,它可以将.dts文件编译成.dtb文件。DTC的源码位于内核的scripts/dtc目录下,内核选中CONFIG_OF,编译内核的时候,主机可执行程序DTC就会被编译出来。

DTB(二进制文件)

DTC编译.dts生成的二进制文件(.dtb),bootloader在引到内核时,会预先读取.dtb到内存,进而由内核解析。

BootLoader(bootloader支持)

Bootloader需要将设备树在内存中的地址传给内核。在ARM中通过bootm或bootz命令来进行传递。

Device Tree组成和结构

device tree的基本单元是node。这些node被组织成树状结构,除了root node,每个node都只有一个parent。一个device tree文件中只能有一个root node。每个node中包含了若干的property/value来描述该node的一些特性。每个node用节点名字(node name)标识,节点名字的格式是node-name@unit-address。如果该node没有reg属性),那么该节点名字中必须不能包括@和unit-address。root node的node name是确定的,必须是“/”。

2. 设备树信息加载

设备树的目的是用于抽象硬件资源,减少代码的重复定义。内核会识别出相应设备树中的节点,并展开出platform_device、i2c_client等各种设备,对应到platform上,设备树上的node会展开为platform_device,设备用到的资源也会由内核绑定给相应设备。

2.1 dtb展开为device_node

Linux最底层的初始化是用汇编实现的,而在C中的start_kernel()中,具体是在setup_arch()中完成初始化的。
setup_arch()中有三个重要的函数,分别是setup_machine_fdt()、arm_memblock_init()以及unflatten_device_tree()。
setup_machine_fdt()函数根据传入的设备树dtb的首地址完成一些初始化操作;arm_memblock_init()函数主要是内存相关,为设备树保留相应的内存空间,保证设备树dtb本身存在于内存中而不被覆盖。用户可以在设备树中设置保留内存,这一部分同时作了保留指定内存的工作;unflatten_device_tree()从命名可以看出,这个函数就是对设备树具体的解析,事实上在这个函数中所做的工作就是将设备树各节点转换成相应的struct device_node结构体。
各函数详细作用可看附录,以后在详细了解后应该会进行补充。

2.2 device_node展开为platform_device

将device_node展开为platform_device是of_platform_default_populate_init()函数实现的。

arch_initcall_sync(of_platform_default_populate_init);

其具体调用为

static int __init of_platform_default_populate_init(void)
	int of_platform_default_populate(struct device_node *root, const struct of_dev_auxdata *lookup,struct device *parent)
		int of_platform_populate(struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent)
			static int of_platform_bus_create(struct device_node *bus, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent, bool strict)
				static struct platform_device *of_platform_device_create_pdata(struct device_node *np, const char *bus_id, void *platform_data, struct device *parent)

of_platform_default_populate_init()在内核中没有直接对它的调用,而是使用放在".initcall3s.init"代码段中,在内核启动时进行 do_initcall_level()时调用这个函数。

static int __init of_platform_default_populate_init(void)
{
	struct device_node *node;

	device_links_supplier_sync_state_pause();

	if (!of_have_populated_dt())
		return -ENODEV;

	/*
	 * Handle certain compatibles explicitly, since we don't want to create
	 * platform_devices for every node in /reserved-memory with a
	 * "compatible",
	 */
	for_each_matching_node(node, reserved_mem_matches) //处理一些保留的节点
		of_platform_device_create(node, NULL, NULL);

	node = of_find_node_by_path("/firmware"); //处理firmware目录下的节点
	if (node) {
		of_platform_populate(node, NULL, NULL, NULL);
		of_node_put(node);	//减少node引用计数
	}

	/* Populate everything else. */
	of_platform_default_populate(NULL, NULL, NULL); //处理其余类型的节点

	return 0;
}

其作用是遍历device_tree,生成platform_device,最主要的处理函数就是of_platform_default_populate(),对应在该函数中,root、lookup与parent均为NULL。

int of_platform_default_populate(struct device_node *root,
				 const struct of_dev_auxdata *lookup,
				 struct device *parent)
{
	return of_platform_populate(root, of_default_bus_match_table, lookup,
				    parent);
}
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()函数会根据node的compatible属性调用of_platform_bus_create()创建device,其中root的值为of_find_node_by_path("/"),并对其子节点分别创建device。matches为上面的of_default_bus_match_table。

int of_platform_populate(struct device_node *root,
			const struct of_device_id *matches,
			const struct of_dev_auxdata *lookup,
			struct device *parent)
{
	struct device_node *child;
	int rc = 0;

	root = root ? of_node_get(root) : of_find_node_by_path("/");
	if (!root)
		return -EINVAL;

	pr_debug("%s()\n", __func__);
	pr_debug(" starting at: %pOF\n", root);

	device_links_supplier_sync_state_pause();
	for_each_child_of_node(root, child) {
		rc = of_platform_bus_create(child, matches, lookup, parent, true);
		if (rc) {
			of_node_put(child);
			break;
		}
	}
	device_links_supplier_sync_state_resume();

	of_node_set_flag(root, OF_POPULATED_BUS);

	of_node_put(root);
	return rc;
}

该函数的作用是根据device_node创建platform_device,并递归的对子节点创建platform_device。首先判断节点是否有compatible属性,没有的话不做处理;接着判断of_match_node()函数,根据of_skipped_node_table表查看节点属性,判断节点是否需要创建设备;如果节点中的compatible属性的值为operating-points-v2,则跳过;接着根据bus总线的值判断该总线节点是否已被展开,如果是,跳过;lookup根据of_platform_default_populate()函数发现值为空,直接返回NULL;接着,amba设备做单独处理;然后调用of_platform_device_create_pdata()函数将节点转化为设备;最后递归地将子节点转化为设备。在总线上的节点转化完毕后,设置标志OF_POPULATED_BUS。
在对子节点进行操作时,根据matchs传入的数组进行判断,如果包括"simple-bus"、“simple-mfd”、“isa”、"arm,amba-bus"等属性时,会创建对子节点创建platform_device。

static int of_platform_bus_create(struct device_node *bus,
				  const struct of_device_id *matches,
				  const struct of_dev_auxdata *lookup,
				  struct device *parent, bool strict)
{
	const struct of_dev_auxdata *auxdata;
	struct device_node *child;
	struct platform_device *dev;
	const char *bus_id = NULL;
	void *platform_data = NULL;
	int rc = 0;

	/* Make sure it has a compatible property */
	if (strict && (!of_get_property(bus, "compatible", NULL))) {
		pr_debug("%s() - skipping %pOF, no compatible prop\n",
			 __func__, bus);
		return 0;
	}

	/* Skip nodes for which we don't want to create devices */
	if (unlikely(of_match_node(of_skipped_node_table, bus))) {
		pr_debug("%s() - skipping %pOF node\n", __func__, bus);
		return 0;
	}

	if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
		pr_debug("%s() - skipping %pOF, already populated\n",
			__func__, bus);
		return 0;
	}

	auxdata = of_dev_lookup(lookup, bus);
	if (auxdata) {
		bus_id = auxdata->name;
		platform_data = auxdata->platform_data;
	}

	if (of_device_is_compatible(bus, "arm,primecell")) {
		/*
		 * Don't return an error here to keep compatibility with older
		 * device tree files.
		 */
		of_amba_device_create(bus, bus_id, platform_data, parent);
		return 0;
	}

	dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
	if (!dev || !of_match_node(matches, bus))
		return 0;

	for_each_child_of_node(bus, child) {
		pr_debug("   create child: %pOF\n", child);
		rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
		if (rc) {
			of_node_put(child);
			break;
		}
	}
	of_node_set_flag(bus, OF_POPULATED_BUS);
	return rc;
}

很明显,在该函数中,of_platform_device_create_pdata()为主要函数,其代码如下。作用是分配
初始化并注册一个设备。

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;

	if (!of_device_is_available(np) ||
	    of_node_test_and_set_flag(np, OF_POPULATED))
		return NULL;

	dev = of_device_alloc(np, bus_id, parent);
	if (!dev)
		goto err_clear_flag;

	dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
	if (!dev->dev.dma_mask)
		dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
	dev->dev.bus = &platform_bus_type;
	dev->dev.platform_data = platform_data;
	of_msi_configure(&dev->dev, dev->dev.of_node);

	if (of_device_add(dev) != 0) {
		platform_device_put(dev);
		goto err_clear_flag;
	}

	return dev;

err_clear_flag:
	of_node_clear_flag(np, OF_POPULATED);
	return NULL;
}

由of_device_alloc()分配一个platform_device型结构体,在对bus、platform_data(此时为NULL)赋值后,由of_device_add()将创建的设备添加到系统中。
对于of_device_alloc(),首先调用platform_device_alloc()创建一个platform_device,接着函数统计设备树中reg属性和中断irq属性的个数,然后分别为它们申请内存空间,链入到platform_device中的struct resources成员中。除了设备树中"reg"和"interrupt"属性之外,还有可选的"reg-names"和"interrupt-names"这些io中断资源相关的设备树节点属性也在这里被转换。

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);
	if (!dev)
		return NULL;
	//统计中断irq属性的数量
	/* count the io and irq resources */
	while (of_address_to_resource(np, num_reg, &temp_res) == 0)
		num_reg++;
	num_irq = of_irq_count(np);	//统计中断irq属性的数量
	//根据num_irq和num_reg的数量申请相应的struct resource内存空间。
	/* Populate the resource table */
	if (num_irq || num_reg) {
		res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);
		if (!res) {
			platform_device_put(dev);
			return NULL;
		}

		dev->num_resources = num_reg + num_irq;
		dev->resource = res;
		//将device_node中的reg属性转换成platform_device中的struct resource成员
		for (i = 0; i < num_reg; i++, res++) {
			rc = of_address_to_resource(np, i, res);
			WARN_ON(rc);
		}
		//将device_node中的irq属性转换成platform_device中的struct resource成员
		if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
			pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
				 np);
	}
	//将platform_device的dev.of_node成员指针指向device_node。
	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);

	return dev;
}

最后调用of_device_add()将相应的设备树节点生成的device_node节点链入到platform_device的dev.of_node中。
设备树的目的是将硬件资源抽象出来,由系统统一解析,避免驱动中对硬件资源大量的重复定义。在老版本的内核中,platform_device部分是静态定义的,其实最主要的部分就是resources部分,这一部分描述了当前驱动需要的硬件资源,一般是IO,中断等资源。在设备树中,这一类资源通常通过reg属性来描述,中断则通过interrupts来描述,所以,设备树中的reg和interrupts资源将会被转换成platform_device内的struct resources资源。在platform_device中有一个成员struct device dev,这个dev中又有一个指针成员struct device_node* of_node,linux的做法就是将这个of_node指针直接指向由设备树转换而来的device_node结构。例如,有这么一个struct platform_device* of_test.我们可以直接通过of_test->dev.of_node来访问设备树中的信息.

3. 设备的匹配

在之前分析的platform_match()中,调用了of_driver_match_device()函数,这个函数就是将驱动与设备树中的设备进行匹配,其具体调用为:

static inline int of_driver_match_device(struct device *dev,const struct device_driver *drv)
	const struct of_device_id *of_match_device(const struct of_device_id *matches,const struct device *dev)
		static const struct of_device_id *__of_match_node(const struct of_device_id *matches, const struct device_node *node)
			static int __of_device_is_compatible(const struct device_node *device, const char *compat, const char *type, const char *name)

首先调用of_driver_match_device()函数进行设备与驱动的匹配,而该函数调用了of_match_device()函数,主要是通过驱动的of_match_table结构体。与设备进行匹配。

static inline int of_driver_match_device(struct device *dev,
					 const struct device_driver *drv)
{
	return of_match_device(drv->of_match_table, dev) != NULL;
}

of_match_table的具体类型如下所示,包含了驱动支持的多个设备类型

struct of_device_id                                      
{
	char    name[32];  //设备名
	char    type[32];  //设备类型
	char    compatible[128];  //与设备信息compatible相匹配
	const void *data;  //驱动私有数据
};

设备树中的设备信息在加载时被组织为device_node结构体,转变为platform_device时,其of_node指针指向的就是该device_node结构。

const struct of_device_id *of_match_device(const struct of_device_id *matches,
					   const struct device *dev)
{
	if ((!matches) || (!dev->of_node))
		return NULL;
	return of_match_node(matches, dev->of_node);
}

其匹配的方式主要是通过compatible、type以及name等方式进行匹配,其优先级在__of_device_is_compatible()函数注释中进行了说明。

const struct of_device_id *__of_match_node(const struct of_device_id *matches,
					   const struct device_node *node)
{
	const struct of_device_id *best_match = NULL;
	int score, best_score = 0;

	if (!matches)
		return NULL;

	for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
		score = __of_device_is_compatible(node, matches->compatible,
						  matches->type, matches->name);
		if (score > best_score) {
			best_match = matches;
			best_score = score;
		}
	}

	return best_match;
}

参数 device 指向一个 device_node; 参数指向 compatible 字符串。函数调用 __of_find_property() 函数获得该设备的属性“compatible”。如果属性compatible找到,那么返回属性compatible的值;否则返回 NULL。调用 of_compat_cmp() 函数对比 compatible 属性的值是否和参数一致。

static int __of_device_is_compatible(const struct device_node *device,
				     const char *compat, const char *type, const char *name)
{
	struct property *prop;
	const char *cp;
	int index = 0, score = 0;

	/* Compatible match has highest priority */
	if (compat && compat[0]) {
		prop = __of_find_property(device, "compatible", NULL);
		for (cp = of_prop_next_string(prop, NULL); cp;
		     cp = of_prop_next_string(prop, cp), index++) {
			if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
				score = INT_MAX/2 - (index << 2);
				break;
			}
		}
		if (!score)
			return 0;
	}

	/* Matching type is better than matching name */
	if (type && type[0]) {
		if (!__of_node_is_type(device, type))
			return 0;
		score += 2;
	}

	/* Matching name is a bit better than not */
	if (name && name[0]) {
		if (!of_node_name_eq(device, name))
			return 0;
		score++;
	}

	return score;
}

由于每个 device_node 包含一个名为 properties 的成员,properties 是一个单链表,包含了该节点的所有属性,函数通过使用 for 循环遍历这个链表,以此遍历节点所包含的所有属性。每遍历一个属性就会调用 of_prop_cmp() 函数, of_prop_cmp() 函数用于对比两个字符串是否相等,如果相等返回 0。因此,当遍历到的属性的名字与参数 name 一致,那么定义为找到了指定的属性。

static struct property *__of_find_property(const struct device_node *np,
					   const char *name, int *lenp)
{
	struct property *pp;

	if (!np)
		return NULL;

	for (pp = np->properties; pp; pp = pp->next) {
		if (of_prop_cmp(pp->name, name) == 0) {
			if (lenp)
				*lenp = pp->length;
			break;
		}
	}

	return pp;
}

在设备匹配方式改变的同时,其driver的初始化也发生了改变,这是基于设备树的设备匹配方式的驱动:

static const struct of_device_id arc_pmu_match[] = {
	{ .compatible = "snps,arc700-pct" },
	{ .compatible = "snps,archs-pct" },
	{},
};
static struct platform_driver arc_pmu_driver = {
	.driver	= {
		.name		= "arc-pct",
		.of_match_table = of_match_ptr(arc_pmu_match),
	},
	.probe		= arc_pmu_device_probe,
};

这是一般platform_device设备匹配的驱动的初始化:

static struct platform_driver db1300_wm97xx_driver = {
	.driver.name	= "wm97xx-touch",
	.driver.owner	= THIS_MODULE,
	.probe		= db1300_wm97xx_probe,
};

针对设备树节点展开为platform_device的过程,主要通过这篇文章进行学习的:

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux设备树使用手册(注释版)是一份非常有用的资源,可以帮助用户了解和掌握Linux设备树的相关知识。这份手册是一个注释版,它包含了非常详细的注释,使得用户可以更快地理解和掌握设备树的使用。 设备树是用于描述硬件信息的一种数据结构。在Linux系统中,设备树是将系统硬件信息映射到内核空间的一种机制。它可以方便地解决硬件之间的依赖关系和相互作用问题。同时,它也可以提高系统的性能和可靠性。 在这份手册中,用户可以学习到如何创建和编译设备树文件、如何在设备树中定义设备节点、如何使用设备节点来控制硬件等等。此外,该手册还提供了相关的代码示例和步骤说明,使得用户可以更加容易地跟随和理解。 总之,Linux设备树使用手册(注释版)是一份非常有用的工具,不仅可以帮助用户更好地理解设备树的概念和原理,也可以帮助用户更快地掌握实际应用。对于需要使用设备树Linux用户来说,这份手册是一个必备的资源。 ### 回答2: Linux设备树使用手册(注释版)是一本对于设备树的使用和理解起到了很好的指导作用的书籍,它适合于想深入了解设备树的开发人员和爱好者使用。该书主要分为基础、高级、特定平台和常用的四个部分,每一部分都涵盖了相应的主题。其中,基础部分介绍了设备树的基本概念和使用方法,如何编写简单的设备树描述以及设备树的编译和加载等;高级篇深入介绍了独立设备树节点和复合设备树节点的使用方法,包括节点的绑定和解绑以及设备树匹配等内容;特定平台部分介绍了一些特定于某种硬件平台的设备树编写方法和使用技巧;最后,常用部分则介绍了一些常用的设备树属性和设备树编写技巧,如设备树引用、属性和绑定等。 该书从注释的角度出发,比较详尽的讲述了设备树的使用方法,同时提供了许多实例和示例代码,有助于读者更好地理解和掌握设备树的使用方法。另外,在书籍的结尾部分,还提供了一些附录,包括设备树的格式和语法,设备树的节点类型和属性等相关内容,对于读者的参考与使用都是非常有帮助的。 总之,该书详实的介绍了Linux设备树的使用方法,不仅适合正在进行设备树开发的开发人员参考,也适合广大爱好者进行学习和了解。同时,由于该书注重实例的使用,也让读者更加直观的感受到了设备树的威力和作用,是一本非常好的实用型书籍。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值