Эта статья основана на ядре Linux версии 5.15 и призвана проанализировать принцип работы и сценарии применения Linux Device Tree Overlay (DTO).
Так называемое «наложение дерева устройств» относится к процессу динамического изменения текущего активного дерева устройств (живого дерева устройств), которое включает добавление или удаление подустройств, а также расширение атрибутов узла устройства. Этот процесс в основном включает в себя два ключевых этапа:
При разработке драйверов устройств ядра Linux традиционный подход заключается в изменении исходного файла дерева устройств (DTS) и кода драйвера, затем компиляции и генерации нового образа ядра, записи его на SSD или другой носитель и, наконец, перезагрузки системы. проверьте корректность драйвера.
Технология наложения дерева устройств позволяет нам динамически изменять активное дерево устройств во время выполнения, что означает, что нет необходимости перекомпилировать дерево устройств или перезапускать систему. Такой механизм значительно повышает гибкость и эффективность разработки и отладки.
Предположим, аппаратная платформа использует устройство A версии V1, а в версии V2 устройство обновляется до устройства B. Если группа разработчиков программного обеспечения платформы поддерживает только один набор баз кода Linux, то с помощью наложения дерева устройств соответствующий файл .dtbo может быть динамически выбран в соответствии с версией оборудования, так что один и тот же набор кода может удовлетворить потребности как V1, так и версии 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 ---------------------------------------------------------------
Фрагмент наложения 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 -----------------------------------------------------------
В результате переопределения создается новый узел устройства (стержень), поэтому устройство платформы стержня будет зарегистрировано, и если соответствующий драйвер устройства загружен, устройство будет создано, как ожидалось.
Если базовое дерево устройств не было скомпилировано с опцией -@,Так"&ocp"Метки не будут использоваться для сопоставления узла наложения с правильным местоположением в базовом дереве устройств.。в этом случае,Можно указать путь назначения. Поскольку переопределение можно применить к любому базовому дереву устройств, содержащему тег,Независимо от того, где в дереве устройств появляется метка,Поэтому для указания целевого местоположения предпочтительнее использовать синтаксис тегов.
Пример приведенного выше файла bar.dts, модифицированного для использования синтаксиса целевого пути:
---- bar.dts - Переопределить целевое местоположение с помощью явного указания пути --------------------------------
/dts-v1/;
/plugin/;
&{/ocp} {
/* периферийные устройства для бара */
bar {
compatible = "corp,bar";
... /* Различные атрибуты и подузлы */
}
};
---- bar.dts ---------------------------------------------------------------
Основной API наложения в основном вызывает следующие два интерфейса для реализации наложения.
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 об изменении платформы, 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 использует технологию цепочки уведомлений ядра. Платформа, spi, i2c и другие шины подписываются на соответствующие события при инициализации инфраструктуры драйвера. При применении dt overlay будет выдано уведомление, и все компоненты, которые подписываются на это уведомление, смогут его получить. к этому сообщению и обработать его соответствующим образом. Если будет обнаружено, что обновление узла не связано с самим собой, оно вернется напрямую. Как этот компонент связан сам с собой?
Соответствующее устройство будет зарегистрировано и будет запущено сопоставление устройства и драйвера устройства.
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 Эти три функции отвечают за создание устройства, соответствующего устройству, тем самым запуская сопоставление устройства и драйвера. Эти три функции связаны с моделью устройства Linux и будут представлены в последующих статьях.
При фактическом использовании наложения дерева устройств основной API наложения может быть инкапсулирован в системный узел, а наложение дерева устройств может быть реализовано путем управления sys узлом.