前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内核设备树覆盖(Device Tree Overlay)原理和使用场景

Linux内核设备树覆盖(Device Tree Overlay)原理和使用场景

原创
作者头像
bingwang
发布2024-03-18 16:39:11
7150
发布2024-03-18 16:39:11
举报
文章被收录于专栏:Linux kernelLinux kernel

Linux内核设备树覆盖(Device Tree Overlay)入门

前言

本文基于Linux kernel 5.15版本进行说明,旨在解析Linux设备树覆盖(Device Tree Overlay, DTO)的工作原理及其应用场景。

设备树覆盖(DTO)原理简介

所谓“设备树覆盖”,指的是对当前活动的设备树(living device tree)进行动态修改的过程,这包括添加或删除子设备,以及扩展某个设备节点的属性。在这一过程中,主要涉及到两个关键步骤:

  1. 如何加载并应用DTBO:首先,需要将设备树覆盖文件(.dtbo)加载到系统中,并将其应用到最终的设备树上。
  2. 如何通知组件创建设备:其次,需要通知相关组件根据更新后的设备树节点来创建具体的设备实例,最终触发设备与设备驱动程序的匹配。

为什么需要设备树覆盖?

在进行Linux内核设备驱动开发时,传统的做法是修改设备树源文件(DTS)和驱动程序代码,随后编译生成新的内核镜像,并将其烧写到SSD或其他存储介质中,最后重启系统以验证驱动程序的正确性。

设备树覆盖技术允许我们在运行时(runtime)阶段动态地修改活动的设备树,这意味着无需重新编译设备树,也不需要重启系统。这样的机制显著提高了开发和调试的灵活性与效率。

应用场景举例

假设一个硬件平台在V1版本时使用了A器件,而在V2版本中,该器件被更新为B设备。如果平台软件团队只维护一套Linux代码库,通过使用设备树覆盖,可以根据硬件版本动态选择合适的.dtbo文件,从而使同一套代码同时满足V1和V2硬件版本的需求,极大地增加了项目的灵活性和可维护性。

总之,设备树覆盖为Linux内核提供了一种高效、灵活的设备管理机制,特别适用于需要动态调整硬件配置或支持多个硬件版本的场景

如何工作:

设备树覆盖的目的是修改内核的实时树,并以反映更改的方式影响内核的状态。 由于内核主要处理设备,任何导致激活设备的新设备节点都应该被创建,而如果设备节点被禁用或完全移除,受影响的设备应该被注销。

以foo板为例,以下是基础树:

代码语言:dts
复制
    ---- foo.dts ---------------------------------------------------------------
	/* FOO platform */
	/dts-v1/;
	/ {
		compatible = "corp,foo";

		/* shared resources */
		res: res {
		};

		/* On chip peripherals */
		ocp: ocp {
			/* peripherals that are always instantiated */
			peripheral1 { ... };
		};
	};
    ---- foo.dts ---------------------------------------------------------------

overlay片段 bar.dts如下

代码语言:dts
复制
    ---- bar.dts - overlay target location by label ----------------------------
	/dts-v1/;
	/plugin/;
	&ocp {
		/* bar peripheral */
		bar {
			compatible = "corp,bar";
			... /* various properties and child nodes */
		};
	};
    ---- bar.dts ---------------------------------------------------------------

合并之后,应该是

代码语言:dts
复制
    ---- foo+bar.dts -----------------------------------------------------------
	/* FOO platform + bar peripheral */
	/ {
		compatible = "corp,foo";

		/* shared resources */
		res: res {
		};

		/* On chip peripherals */
		ocp: ocp {
			/* peripherals that are always instantiated */
			peripheral1 { ... };

			/* bar peripheral */
			bar {
				compatible = "corp,bar";
				... /* various properties and child nodes */
			};
		};
	};
    ---- foo+bar.dts -----------------------------------------------------------

作为覆盖的结果,一个新的设备节点(bar)被创建了,因此一个bar平台设备将被注册,如果加载了匹配的设备驱动,设备将按预期被创建。

如果基础设备树没有使用-@选项编译,那么"&ocp"标签将不可用于将覆盖节点解析到基础设备树中的正确位置。在这种情况下,可以提供目标路径。因为覆盖可以应用到任何包含该标签的基础设备树上,无论该标签出现在设备树的哪个位置,所以更倾向于使用标签语法指定目标位置。

上述修改为使用目标路径语法的bar.dts示例是:

代码语言:dts
复制
---- bar.dts - 通过显式路径指定覆盖目标位置 --------------------------------
/dts-v1/;
/plugin/;
&{/ocp} {
	/* bar外设 */
	bar {
		compatible = "corp,bar";
		... /* 各种属性和子节点 */
	}
};
---- bar.dts ---------------------------------------------------------------

overlay的核心API,主要是调用下面的两个接口实现overlay

代码语言:C
复制
int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,
			 int *ret_ovcs_id)
int of_overlay_remove(int *ovcs_id)

of_overlay_fdt_apply这个函数,代码中主要分为两个关键步骤,首先,是把dtbo文件的内容,插入到当前的设备树中去,然后发出通知,通知各个组件Device Tree发生了变化。platform, spi, i2c等核心层订阅了这个消息,也就是实现了各自的notifier_call的回调函数。

代码语言:C
复制
int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size, int *ret_ovcs_id)
		overlay_mem = of_fdt_unflatten_tree(new_fdt_align, NULL, &ovcs->overlay_root);
		ret = of_overlay_apply(ovcs);
			ret = of_resolve_phandles(ovcs->overlay_root);
			ret = init_overlay_changeset(ovcs);
			ret = overlay_notify(ovcs, OF_OVERLAY_PRE_APPLY);
			ret = build_changeset(ovcs);
			ret = __of_changeset_apply_entries(&ovcs->cset, &ret_revert);
			ret = __of_changeset_apply_notify(&ovcs->cset);
				ret_tmp = __of_changeset_entry_notify(ce, 0);
					ret = of_reconfig_notify(ce->action, &rd);
						rc = blocking_notifier_call_chain(&of_reconfig_chain, action, p)
							ret = nb->notifier_call(nb, val, v);
					ret = of_property_notify(ce->action, ce->np, ce->prop, ce->old_prop);
			ret_tmp = overlay_notify(ovcs, OF_OVERLAY_POST_APPLY);

overlay使用了内核通知链的技术,platform, spi,i2c 等总线分别在驱动框架初始化的时候,订阅了相关事件,当dt overlay apply的时候,会发出通知,所有订阅了这个通知的组件都能够收到这个消息,并作出相应的处理。如果发现不是和自己相关的节点更新,则直接返回。如何是和自己相关的组件,

则会去注册对应的device,并触发device和 device driver的匹配。

代码语言:C
复制
gpiolib.c	4395 WARN_ON(of_reconfig_notifier_register(&gpio_of_notifier)); in gpiolib_dev_init()
spi.c	4344 WARN_ON(of_reconfig_notifier_register(&spi_of_notifier)); in spi_init()
platform.c	730 WARN_ON(of_reconfig_notifier_register(&platform_of_notifier)); in of_platform_register_reconfig_notifier()
i2c-core-base.c	1985 WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier)); in i2c_init()

platform_of_notifier的回调如下,注意14行代码,如果不是自己的相关的直接返回了。

代码语言:C
复制
#ifdef CONFIG_OF_DYNAMIC
static int of_platform_notify(struct notifier_block *nb,
				unsigned long action, void *arg)
{
	struct of_reconfig_data *rd = arg;
	struct platform_device *pdev_parent, *pdev;
	bool children_left;
	int ret;

	switch (of_reconfig_get_state_change(action, rd)) {
	case OF_RECONFIG_CHANGE_ADD:
		/* verify that the parent is a bus */
		if (!of_node_check_flag(rd->dn->parent, OF_POPULATED_BUS))
			return NOTIFY_OK;	/* not for us */

		/* already populated? (driver using of_populate manually) */
		if (of_node_check_flag(rd->dn, OF_POPULATED))
			return NOTIFY_OK;

		/* pdev_parent may be NULL when no bus platform device */
		pdev_parent = of_find_device_by_node(rd->dn->parent);
		ret = of_platform_bus_create(rd->dn, of_default_bus_match_table,
					     NULL, pdev_parent ?
					     &pdev_parent->dev : NULL, true);
		platform_device_put(pdev_parent);

		if (ret) {
			pr_err("%s: failed to create for '%pOF'\n",
					__func__, rd->dn);
			/* of_platform_device_create tosses the error code */
			return notifier_from_errno(ret);
		}
		break;

	case OF_RECONFIG_CHANGE_REMOVE:

		/* already depopulated? */
		if (!of_node_check_flag(rd->dn, OF_POPULATED))
			return NOTIFY_OK;

		/* find our device by node */
		pdev = of_find_device_by_node(rd->dn);
		if (pdev == NULL)
			return NOTIFY_OK;	/* no? not meant for us */

		/* unregister takes one ref away */
		of_platform_device_destroy(&pdev->dev, &children_left);

		/* and put the reference of the find */
		platform_device_put(pdev);
		break;
	}

	return NOTIFY_OK;
}

static struct notifier_block platform_of_notifier = {
	.notifier_call = of_platform_notify,
};

spi_of_notifier的实现,如下:

代码语言:C
复制
static int of_spi_notify(struct notifier_block *nb, unsigned long action,
			 void *arg)
{
	struct of_reconfig_data *rd = arg;
	struct spi_controller *ctlr;
	struct spi_device *spi;

	switch (of_reconfig_get_state_change(action, arg)) {
	case OF_RECONFIG_CHANGE_ADD:
		ctlr = of_find_spi_controller_by_node(rd->dn->parent);
		if (ctlr == NULL)
			return NOTIFY_OK;	/* not for us */

		if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) {
			put_device(&ctlr->dev);
			return NOTIFY_OK;
		}

		spi = of_register_spi_device(ctlr, rd->dn);
		put_device(&ctlr->dev);

		if (IS_ERR(spi)) {
			pr_err("%s: failed to create for '%pOF'\n",
					__func__, rd->dn);
			of_node_clear_flag(rd->dn, OF_POPULATED);
			return notifier_from_errno(PTR_ERR(spi));
		}
		break;

	case OF_RECONFIG_CHANGE_REMOVE:
		/* already depopulated? */
		if (!of_node_check_flag(rd->dn, OF_POPULATED))
			return NOTIFY_OK;

		/* find our device by node */
		spi = of_find_spi_device_by_node(rd->dn);
		if (spi == NULL)
			return NOTIFY_OK;	/* no? not meant for us */

		/* unregister takes one ref away */
		spi_unregister_device(spi);

		/* and put the reference of the find */
		put_device(&spi->dev);
		break;
	}

	return NOTIFY_OK;
}

static struct notifier_block spi_of_notifier = {
	.notifier_call = of_spi_notify,
};

i2c_of_notifier的实现如下:

代码语言:C
复制
#if IS_ENABLED(CONFIG_OF_DYNAMIC)
static int of_i2c_notify(struct notifier_block *nb, unsigned long action,
			 void *arg)
{
	struct of_reconfig_data *rd = arg;
	struct i2c_adapter *adap;
	struct i2c_client *client;

	switch (of_reconfig_get_state_change(action, rd)) {
	case OF_RECONFIG_CHANGE_ADD:
		adap = of_find_i2c_adapter_by_node(rd->dn->parent);
		if (adap == NULL)
			return NOTIFY_OK;	/* not for us */

		if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) {
			put_device(&adap->dev);
			return NOTIFY_OK;
		}

		client = of_i2c_register_device(adap, rd->dn);
		if (IS_ERR(client)) {
			dev_err(&adap->dev, "failed to create client for '%pOF'\n",
				 rd->dn);
			put_device(&adap->dev);
			of_node_clear_flag(rd->dn, OF_POPULATED);
			return notifier_from_errno(PTR_ERR(client));
		}
		put_device(&adap->dev);
		break;
	case OF_RECONFIG_CHANGE_REMOVE:
		/* already depopulated? */
		if (!of_node_check_flag(rd->dn, OF_POPULATED))
			return NOTIFY_OK;

		/* find our device by node */
		client = of_find_i2c_device_by_node(rd->dn);
		if (client == NULL)
			return NOTIFY_OK;	/* no? not meant for us */

		/* unregister takes one ref away */
		i2c_unregister_device(client);

		/* and put the reference of the find */
		put_device(&client->dev);
		break;
	}

	return NOTIFY_OK;
}

struct notifier_block i2c_of_notifier = {
	.notifier_call = of_i2c_notify,
};

of_platform_bus_create,of_register_spi_device, of_i2c_register_device 这三个函数负责创建设备对应的设备,从而触发device和driver的匹配。这三个函数涉及到Linux设备模型,在后续的文章会继续介绍。

在实际使用device tree overlay的时候,可以将overlay的核心API封装成sys节点,通过操作sys节点的方式,实现device tree的overlay。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Linux内核设备树覆盖(Device Tree Overlay)入门
    • 前言
    • 设备树覆盖(DTO)原理简介
    • 为什么需要设备树覆盖?
      • 应用场景举例:
      • 如何工作:
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
      http://www.vxiaotou.com