本文基于Linux kernel 5.15版本进行说明,旨在解析Linux设备树覆盖(Device Tree Overlay, DTO)的工作原理及其应用场景。
所谓“设备树覆盖”,指的是对当前活动的设备树(living device tree)进行动态修改的过程,这包括添加或删除子设备,以及扩展某个设备节点的属性。在这一过程中,主要涉及到两个关键步骤:
在进行Linux内核设备驱动开发时,传统的做法是修改设备树源文件(DTS)和驱动程序代码,随后编译生成新的内核镜像,并将其烧写到SSD或其他存储介质中,最后重启系统以验证驱动程序的正确性。
设备树覆盖技术允许我们在运行时(runtime)阶段动态地修改活动的设备树,这意味着无需重新编译设备树,也不需要重启系统。这样的机制显著提高了开发和调试的灵活性与效率。
假设一个硬件平台在V1版本时使用了A器件,而在V2版本中,该器件被更新为B设备。如果平台软件团队只维护一套Linux代码库,通过使用设备树覆盖,可以根据硬件版本动态选择合适的.dtbo文件,从而使同一套代码同时满足V1和V2硬件版本的需求,极大地增加了项目的灵活性和可维护性。
总之,设备树覆盖为Linux内核提供了一种高效、灵活的设备管理机制,特别适用于需要动态调整硬件配置或支持多个硬件版本的场景
设备树覆盖的目的是修改内核的实时树,并以反映更改的方式影响内核的状态。 由于内核主要处理设备,任何导致激活设备的新设备节点都应该被创建,而如果设备节点被禁用或完全移除,受影响的设备应该被注销。
以foo板为例,以下是基础树:
---- 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如下
---- bar.dts - overlay target location by label ----------------------------
/dts-v1/;
/plugin/;
&ocp {
/* bar peripheral */
bar {
compatible = "corp,bar";
... /* various properties and child nodes */
};
};
---- bar.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示例是:
---- bar.dts - 通过显式路径指定覆盖目标位置 --------------------------------
/dts-v1/;
/plugin/;
&{/ocp} {
/* bar外设 */
bar {
compatible = "corp,bar";
... /* 各种属性和子节点 */
}
};
---- bar.dts ---------------------------------------------------------------
overlay的核心API,主要是调用下面的两个接口实现overlay
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
的回调函数。
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的匹配。
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行代码,如果不是自己的相关的直接返回了。
#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的实现,如下:
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的实现如下:
#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 删除。