Linux设备树详解(四) 内核解析DTB成platfrom_device
一、背景
在上一节中讲到设备树dtb文件中的各个节点转换成device_node的过程(dtb转换成device_node ),每个设备树子节点都将转换成一个对应的device_node节点。
设备树dts文件最终在linux内核中会转化成platform_device:dts->dtb ->device_node->platform_device。
那么,接下来,我们就来看看linux内核如何把device_node转换成platfrom_device。
平台:arm
内核版本:Linux 5.19
二、device_node转换为platform_device的过程
三、设备树对于驱动
设备树的产生就是为了替代driver中过多的platform_device部分的静态定义,将硬件资源抽象出来,由系统统一解析,这样就可以避免各驱动中对硬件资源大量的重复定义。
这样一来,几乎可以肯定的是,设备树中的节点最终目标是转换成platform device结构,在驱动开发时就只需要添加相应的platform driver部分进行匹配即可。
四、device_node转换为platform_device是有条件的
首先,对于所有的device_node,如果要转换成platform_device,除了节点中必须有compatible属性以外,必须满足以下条件:
- 一般情况下,只对设备树中根的第1级节点(/xx)注册成platform device,也就是对它们的子节点(/xx/*)并不处理。
- 如果一个结点的 compatile 属性含有这些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus")之一,并且自己成功注册成了platform_device,, 那么它的子结点(需含compatile属性)也可以转换为platform_device(当成总线看待)。
- 根节点(/)是例外的,生成platfrom_device时,即使有compatible属性也不会处理
五、设备树结点的什么属性会被转换?
如果是device_node转换成platform device,这个转换过程又是怎么样的呢?在老版本的内核中,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来访问设备树中的信息。
六、platform_device转换的开始
6.1 of_platform_default_populate_init
函数的执行入口是,在系统启动的早期进行的 of_platform_default_populate_init, 该函数在文件drivers/of/platform.c中定义,具体代码如下:
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;
if (IS_ENABLED(CONFIG_PPC)) {
struct device_node *boot_display = NULL;
struct platform_device *dev;
int ret;
/* Check if we have a MacOS display without a node spec */
if (of_get_property(of_chosen, "linux,bootx-noscreen", NULL)) {
/*
* The old code tried to work out which node was the MacOS
* display based on the address. I'm dropping that since the
* lack of a node spec only happens with old BootX versions
* (users can update) and with this code, they'll still get
* a display (just not the palette hacks).
*/
dev = platform_device_alloc("bootx-noscreen", 0);
if (WARN_ON(!dev))
return -ENOMEM;
ret = platform_device_add(dev);
if (WARN_ON(ret)) {
platform_device_put(dev);
return ret;
}
}
/*
* For OF framebuffers, first create the device for the boot display,
* then for the other framebuffers. Only fail for the boot display;
* ignore errors for the rest.
*/
for_each_node_by_type(node, "display") {
if (!of_get_property(node, "linux,opened", NULL) ||
!of_get_property(node, "linux,boot-display", NULL))
continue;
dev = of_platform_device_create(node, "of-display", NULL);
if (WARN_ON(!dev))
return -ENOMEM;
boot_display = node;
break;
}
for_each_node_by_type(node, "display") {
if (!of_get_property(node, "linux,opened", NULL) || node == boot_display)
continue;
of_platform_device_create(node, "of-display", NULL);
}
} else {
/*
* 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");
if (node) {
of_platform_populate(node, NULL, NULL, NULL);
of_node_put(node);
}
node = of_get_compatible_child(of_chosen, "simple-framebuffer");
of_platform_device_create(node, NULL, NULL);
of_node_put(node);
/* Populate everything else. */
of_platform_default_populate(NULL, NULL, NULL);
}
return 0;
}
arch_initcall_sync(of_platform_default_populate_init);
在函数of_platform_default_populate_init()中,调用了 of_platform_default_populate(NULL, NULL, 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);
}
EXPORT_SYMBOL_GPL(of_platform_default_populate);
of_platform_default_populate()调用了of_platform_populate(),我们注意下 of_default_bus_match_table。
6.2 of_default_bus_match_table
of_default_bus_match_table()在文件drivers/of/platform.c中定义,具体代码如下:
/* drivers/of/platform.c */
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 */
};
如果节点的属性值为 "simple-bus","simple-mfd","isa","arm,amba-bus "之一的话,那么它子节点就可以转化成platform_device。
6.3 of_platform_populate
of_platform_populate()在文件drivers/of/platform.c中定义,具体代码如下:
/**
* of_platform_populate() - Populate platform_devices from device tree data
* @root: parent of the first level to probe or NULL for the root of the tree
* @matches: match table, NULL to use the default
* @lookup: auxdata table for matching id and platform_data with device nodes
* @parent: parent to hook devices from, NULL for toplevel
*
* Similar to of_platform_bus_probe(), this function walks the device tree
* and creates devices from nodes. It differs in that it follows the modern
* convention of requiring all device nodes to have a 'compatible' property,
* and it is suitable for creating devices which are children of the root
* node (of_platform_bus_probe will only create children of the root which
* are selected by the @matches argument).
*
* New board support should be using this function instead of
* of_platform_bus_probe().
*
* Return: 0 on success, < 0 on failure.
*/
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) {
// 然后对每个根目录下的一级子节点 创建 bus
// 例如, /r1 , /r2,而不是 /r1/s1
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;
}
EXPORT_SYMBOL_GPL(of_platform_populate);
在调用of_platform_populate()时传入了的matches参数是of_default_bus_match_table[];
这个table是一个静态数组,这个静态数组中定义了一系列的compatible属性:"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"。
按照我们上文中的描述,当某个根节点下的一级子节点的compatible属性为这些属性其中之一时,它的一级子节点也将由device_node转换成platform_device。
到底是不是这样呢?接着往下看。
6.4 of_platform_bus_create
of_platform_bus_create()在文件drivers/of/platform.c中定义,具体代码如下:
/**
* of_platform_bus_create() - Create a device for a node and its children.
* @bus: device node of the bus to instantiate
* @matches: match table for bus nodes
* @lookup: auxdata table for matching id and platform_data with device nodes
* @parent: parent for new device, or NULL for top level.
* @strict: require compatible property
*
* Creates a platform_device for the provided device_node, and optionally
* recursively create devices for all the child nodes.
*/
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;
}
....
// 创建of_platform_device、赋予私有数据
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
// 判断当前节点的compatible属性是否包含上文中compatible静态数组中的元素
// 如果不包含,函数返回0,即,不处理子节点。
if (!dev || !of_match_node(matches, bus))
return 0;
for_each_child_of_node(bus, child) {
pr_debug(" create child: %pOF\n", child);
// 创建 of_platform_bus
/*
如果当前compatible属性中包含静态数组中的元素,
即当前节点的compatible属性有"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"其中一项,
把子节点当作对应的总线来对待,递归地对当前节点调用`of_platform_bus_create()`
即,将符合条件的子节点转换为platform_device结构。
*/
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;
}
6.5 of_platform_device_create_pdata
这个函数实现了:创建of_platform_device、赋予私有数据 此时的参数platform_data为NULL。
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.
*
* Return: 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;
...
// 创建实例,将传入的device_node链接到成员:dev.of_node中
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;
// 登记到 platform 中
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_msi_configure(&dev->dev, dev->dev.of_node);
// 添加当前生成的platform_device。
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;
}
6.6 of_device_alloc
of_device_alloc()在文件drivers/of/platform.c中定义,具体代码如下:
/**
* of_device_alloc - Allocate and initialize an of_device
* @np: device node to assign to device
* @bus_id: Name to assign to the device. May be null to use default name.
* @parent: Parent device.
*/
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;
struct resource *res, temp_res;
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
if (!dev)
return NULL;
/* count the io resources */
//统计reg属性的数量
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
/* Populate the resource table */
//根据num_reg的数量申请相应的struct resource内存空间。
if (num_reg) {
res = kcalloc(num_reg, sizeof(*res), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}
//设置platform_device中的num_resources成员
dev->num_resources = num_reg;
//设置platform_device中的resource成员
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);
}
}
//将platform_device的dev.of_node成员指针指向device_node
dev->dev.of_node = of_node_get(np);
//将platform_device的dev.fwnode成员指针指向device_node的fwnode成员
dev->dev.fwnode = &np->fwnode;
//设备parent为platform_bus
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;
}
EXPORT_SYMBOL(of_device_alloc);
首先,函数先统计设备树中reg属性的个数,然后分别为它们申请内存空间,链入到platform_device中的struct resources成员中。
除了设备树中"reg"和"interrupt"属性之外,还有可选的"reg-names"和"interrupt-names"这些io中断资源相关的设备树节点属性也在这里被转换。
将相应的设备树节点生成的device_node节点链入到platform_device的dev.of_node中。
最终,我们能够通过在自己的驱动中,使用struct device_node *node = pdev->dev.of_node;获取到设备树节点中的数据。
6.7 of_device_add
of_device_add()在文件drivers/of/device.c中定义,具体代码如下:
int of_device_add(struct platform_device *ofdev)
{
BUG_ON(ofdev->dev.of_node == NULL);
/* name and id have to be set so that the platform bus doesn't get
* confused on matching */
ofdev->name = dev_name(&ofdev->dev);
ofdev->id = PLATFORM_DEVID_NONE;
/*
* If this device has not binding numa node in devicetree, that is
* of_node_to_nid returns NUMA_NO_NODE. device_add will assume that this
* device is on the same node as the parent.
*/
set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));
return device_add(&ofdev->dev);
}
将当前platform_device中的struct device成员注册到系统device中,并为其在用户空间创建相应的访问节点。
这一步会调用platform_match,因此最终也会执行设备树的match,以及probe。
- 分享
- 举报
-
浏览量:1086次2023-08-11 18:18:49
-
浏览量:1112次2023-08-10 14:03:57
-
浏览量:1991次2024-01-15 16:17:45
-
浏览量:1028次2023-08-10 11:15:37
-
浏览量:1045次2023-11-29 12:31:57
-
浏览量:707次2024-01-10 14:28:04
-
浏览量:2347次2024-03-18 15:00:34
-
浏览量:626次2023-08-14 17:53:58
-
浏览量:1442次2023-12-06 12:30:38
-
浏览量:1093次2024-02-04 17:43:11
-
浏览量:3415次2022-05-27 10:41:52
-
浏览量:728次2023-12-06 16:50:25
-
浏览量:2425次2023-06-12 14:34:18
-
浏览量:6724次2021-06-17 15:14:12
-
浏览量:990次2023-12-01 12:14:35
-
浏览量:512次2024-02-21 17:08:25
-
浏览量:1375次2023-06-12 14:33:52
-
浏览量:4431次2020-11-14 14:33:28
-
浏览量:1136次2024-02-05 11:02:54
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
Debug
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明