Linux 内核:设备树(3)把device_node转换成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。

原文(有删改):https://www.cnblogs.com/downey-blog/p/10486568.html

基于arm平台,linux4.14

设备树对于驱动

设备树的产生就是为了替代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转换的开始

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; if (!of_have_populated_dt())
return -ENODEV; /*
* Handle ramoops explicitly, since it is inside /reserved-memory,
* which lacks a "compatible" property.
*/
node = of_find_node_by_path("/reserved-memory");
if (node) {
node = of_find_compatible_node(node, NULL, "ramoops");
if (node)
of_platform_device_create(node, NULL, NULL);
} /* 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);
}

of_platform_default_populate()调用了of_platform_populate(),我们注意下of_default_bus_match_table

of_default_bus_match_table

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。

of_platform_populate

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; // 从设备树中获取根节点的device_node结构体
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); //遍历所有的子节点
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;
}
}
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.

到底是不是这样呢?接着往下看。

of_platform_bus_create

/**
* 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; // ... // 创建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;
}

of_platform_device_create_pdata

这个函数实现了:创建of_platform_device、赋予私有数据

此时的参数platform_data为NULL。

/**
* 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.
*
* Returns 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; // 登记到 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;
}
of_device_alloc
struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)
{
//统计reg属性的数量
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
//统计中断irq属性的数量
num_irq = of_irq_count(np);
//根据num_irq和num_reg的数量申请相应的struct resource内存空间。
if (num_irq || num_reg) {
res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}
//设置platform_device中的num_resources成员
dev->num_resources = num_reg + num_irq;
//设置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);
}
//将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 %s\n",
np->name);
}
//将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;
}

首先,函数先统计设备树中reg属性和中断irq属性的个数,然后分别为它们申请内存空间,链入到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;获取到设备树节点中的数据。

of_device_add
int of_device_add(struct platform_device *ofdev){
// ...
return device_add(&ofdev->dev);
}

将当前platform_device中的struct device成员注册到系统device中,并为其在用户空间创建相应的访问节点。

这一步会调用platform_match,因此最终也会执行设备树的match,以及probe。

总结

总的来说,将device_node转换为platform_device的过程还是比较简单的。

整个转换过程的函数调用关系是这样的:

                            of_platform_default_populate_init()
|
of_platform_default_populate();
|
of_platform_populate();
|
of_platform_bus_create()
_____________________|_________________
| |
of_platform_device_create_pdata() of_platform_bus_create()
_________________|____________________
| |
of_device_alloc() of_device_add()

Linux 内核:设备树(3)把device_node转换成platfrom_device的更多相关文章

  1. 设备树处理之——device_node转换成platform_device【转】

    转自:https://www.cnblogs.com/downey-blog/p/10486568.html 以下讨论基于linux4.14,arm平台 platform device 设备树的产生就 ...

  2. Linux内核 设备树操作常用API【转】

    转自:https://www.linuxidc.com/Linux/2017-02/140818.htm 一文中介绍了设备树的语法,这里主要介绍内核中提供的操作设备树的API,这些API通常都在&qu ...

  3. Linux内核 设备树操作常用API

    Linux设备树语法详解一文中介绍了设备树的语法,这里主要介绍内核中提供的操作设备树的API,这些API通常都在"include/of.h"中声明. device_node 内核中 ...

  4. linux设备驱动程序-设备树(2)-device_node转换成platform_device

    设备树处理之--device_node转换成platform_device 以下讨论基于linux4.14,arm平台 platform device 设备树的产生就是为了替代driver中过多的pl ...

  5. (转)Linux内核基数树应用分析

    Linux内核基数树应用分析 ——lvyilong316 基数树(Radix tree)可看做是以二进制位串为关键字的trie树,是一种多叉树结构,同时又类似多层索引表,每个中间节点包含指向多个节点的 ...

  6. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

  7. Linux dts 设备树详解(一) 基础知识

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 1 前言 2 概念 2.1 什么是设备树 dts(device tree)? 2. ...

  8. Linux 获取设备树源文件(DTS)里描述的资源

    Linux 获取设备树源文件(DTS)里的资源 韩大卫@吉林师范大学 在linux使用platform_driver_register() 注册 platform_driver 时, 需要在 plat ...

  9. Linux 获取设备树源文件(DTS)里的资源【转】

    本文转载自:http://blog.csdn.net/keleming1/article/details/51036000 http://www.cnblogs.com/dyllove98/archi ...

  10. Linux c字符串中不可打印字符转换成16进制

    本文由 www.169it.com 搜集整理 如果一个C字符串中同时包含可打印和不可打印的字符,如果想将这个字符串写入文件,同时方便打开文件查看或者在控制台中打印出来不会出现乱码,那么可以将字符串中的 ...

随机推荐

  1. 一文搞懂Maven配置,从此不再糊涂下载依赖(文末有成品)

    一般来说Maven都是配合着idea一起使用,下载依赖速度慢就去网上找个镜像配置一下,但总会遇到莫名其妙的问题,比如镜像源不生效.Error reading file pom.xml等等.今天详细讲解 ...

  2. deepin下的系统,如何为root用户添加密码

  3. linux打包压缩工具详解

    linux打包压缩工具详解 目录 linux打包压缩工具详解 1.linux文件压缩工具 1.1 compress命令详解 1.2 gzip命令详解 1.3 bzip2命令详解 1.4 xz命令详解 ...

  4. Git基本操作命令大全

    一.全局配置命令 ## 配置级别: –local(默认,高级优先):只影响本地仓库 –global(中优先级):只影响所有当前用户的git仓库 –system(低优先级):影响到全系统的git仓库 # ...

  5. Wordpress给每一个分类栏目定制不同的广告位

    给分类栏目添加广告位,等同于添加自定义字段. 如果需要依据不同的栏目给广告位添加不同的tag来源,需要在模板页面中获取栏目的分类别名,读取不同的广告. 图1 如图1所示添加新的图片输入框 1. 实现的 ...

  6. python教程3.2:字典

    字典相比较列表,优势:查找key的需求,列表需要遍历,字典查找速度很快,很方便, 定义  特性 查找.增加和修改操作  删除操作 循环操作  全局函数

  7. JDBC连接MySQL 8时报错:MySQLNonTransientConnectionException: Public Key Retrieval is not allowed

    需要设置属性 IDEA DBerver

  8. 5GC 关键技术之 MEC 边缘计算

    目录 文章目录 目录 前文列表 MEC 边缘计算 ETSI MEC 标准化参考模型 MEC 架构设计原则 MEC 分层架构 MEC 系统架构 MEC 软件架构 MEC in NFV 融合架构 ETSI ...

  9. PhiData 一款开发AI搜索、agents智能体和工作流应用的AI框架

    引言 在人工智能领域,构建一个能够理解并响应用户需求的智能助手是一项挑战性的任务.PhiData作为一个开源框架,为开发者提供了构建具有长期记忆.丰富知识和强大工具的AI助手的可能性.本文将介绍Phi ...

  10. VMware Ubuntu虚拟机打开报错问题

    问题描述 昨天虚拟机卡死,我把VMware Workstation的进程用任务管理器杀掉了,今天重新打开虚拟机却发现以下报错 报错内容 另一个程序已锁定文件的一部分,进程无法访问 打不开磁盘" ...