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. net core下链路追踪skywalking安装和简单使用

    当我们用很多服务时,各个服务间的调用关系是怎么样的?各个服务单调用的顺序\时间性能怎么样?服务出错了,到底是哪个服务引起的?这些问题我们用什么方案解决呢,以前的方式是各个系统自己单独做日志,出了问题从 ...

  2. 利用PostMan 模拟上传/下载文件

    我们经常用postman模拟各种http请求.但是有时候因为业务需要,我们需要测试上传下载功能.其实postman也是很好支持这两种操作的. 一.上传文件: 1.打开postman 选择对应reque ...

  3. laravel-cms学习笔记

    学习地址: https://www.houdunren.com/edu/video/12045 laravel 文档地址: https://gitee.com/houdunren/code/blob/ ...

  4. vscode插件安装和配置支持vue3

    一.常用插件介绍 1.插件Vue 3 Snippets 作用:用于vue3的智能代码提示,语法高亮.智能感知.Emmet等.替代Vetur插件,Vetur在vue2时期比较流行. 常用命令:vuein ...

  5. GPS坐标、火星坐标、百度坐标之间的转换--提供java版本转换代码

    参考文章:https://www.jianshu.com/p/c39a2c72dc65?from=singlemessage 1.国内几种常用坐标系说明 (1)名词解释 坐标系统:用于定位的系统,就跟 ...

  6. Linux(二):Linux的灵魂

    上次说Linux的前世今生的时候,提了一句,就像学习java一样,我们有一个核心的准则 "万物皆对象" ,学习Linux,同样有基本准则,这也是Linux的最基本的特点,那就是&q ...

  7. 渐变颜色css设置

    小说付费章节渐变颜色配置 position: absolute; top: 0; left: 0; width: 100%; height: 211px; transform: translateY( ...

  8. C数据结构:树和森林存储方式与遍历方式

    文章目录 树的存储方式 双亲表示法 孩子链表表示法 孩子兄弟表示法(二叉树表示法) 树和二叉树的转换 森林和二叉树的转换 树和森林的遍历 树的遍历方式 森林的遍历方式 浅谈一下几个问题 为什么树没有中 ...

  9. pageoffice6提取word指定位置(数据区域)的值

    在实际的开发过程中,经常会遇到提取Word文档中指定位置的数据保存到数据库中的需求,PageOffice客户端控件即支持在线保存Word文件,也支持Word文档中的指定位置的数据或所有的数据提交到服务 ...

  10. 基于uniapp+vue3自定义增强版table表格组件「兼容H5+小程序+App端」

    vue3+uniapp多端自定义table组件|uniapp加强版综合表格组件 uv3-table:一款基于uniapp+vue3跨端自定义手机端增强版表格组件.支持固定表头/列.边框.斑马纹.单选/ ...