Linux 内核:设备树(3)把device_node转换成platfrom_device
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的更多相关文章
- 设备树处理之——device_node转换成platform_device【转】
转自:https://www.cnblogs.com/downey-blog/p/10486568.html 以下讨论基于linux4.14,arm平台 platform device 设备树的产生就 ...
- Linux内核 设备树操作常用API【转】
转自:https://www.linuxidc.com/Linux/2017-02/140818.htm 一文中介绍了设备树的语法,这里主要介绍内核中提供的操作设备树的API,这些API通常都在&qu ...
- Linux内核 设备树操作常用API
Linux设备树语法详解一文中介绍了设备树的语法,这里主要介绍内核中提供的操作设备树的API,这些API通常都在"include/of.h"中声明. device_node 内核中 ...
- linux设备驱动程序-设备树(2)-device_node转换成platform_device
设备树处理之--device_node转换成platform_device 以下讨论基于linux4.14,arm平台 platform device 设备树的产生就是为了替代driver中过多的pl ...
- (转)Linux内核基数树应用分析
Linux内核基数树应用分析 ——lvyilong316 基数树(Radix tree)可看做是以二进制位串为关键字的trie树,是一种多叉树结构,同时又类似多层索引表,每个中间节点包含指向多个节点的 ...
- Linux dts 设备树详解(二) 动手编写设备树dts
Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...
- Linux dts 设备树详解(一) 基础知识
Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 1 前言 2 概念 2.1 什么是设备树 dts(device tree)? 2. ...
- Linux 获取设备树源文件(DTS)里描述的资源
Linux 获取设备树源文件(DTS)里的资源 韩大卫@吉林师范大学 在linux使用platform_driver_register() 注册 platform_driver 时, 需要在 plat ...
- Linux 获取设备树源文件(DTS)里的资源【转】
本文转载自:http://blog.csdn.net/keleming1/article/details/51036000 http://www.cnblogs.com/dyllove98/archi ...
- Linux c字符串中不可打印字符转换成16进制
本文由 www.169it.com 搜集整理 如果一个C字符串中同时包含可打印和不可打印的字符,如果想将这个字符串写入文件,同时方便打开文件查看或者在控制台中打印出来不会出现乱码,那么可以将字符串中的 ...
随机推荐
- net core下链路追踪skywalking安装和简单使用
当我们用很多服务时,各个服务间的调用关系是怎么样的?各个服务单调用的顺序\时间性能怎么样?服务出错了,到底是哪个服务引起的?这些问题我们用什么方案解决呢,以前的方式是各个系统自己单独做日志,出了问题从 ...
- 利用PostMan 模拟上传/下载文件
我们经常用postman模拟各种http请求.但是有时候因为业务需要,我们需要测试上传下载功能.其实postman也是很好支持这两种操作的. 一.上传文件: 1.打开postman 选择对应reque ...
- laravel-cms学习笔记
学习地址: https://www.houdunren.com/edu/video/12045 laravel 文档地址: https://gitee.com/houdunren/code/blob/ ...
- vscode插件安装和配置支持vue3
一.常用插件介绍 1.插件Vue 3 Snippets 作用:用于vue3的智能代码提示,语法高亮.智能感知.Emmet等.替代Vetur插件,Vetur在vue2时期比较流行. 常用命令:vuein ...
- GPS坐标、火星坐标、百度坐标之间的转换--提供java版本转换代码
参考文章:https://www.jianshu.com/p/c39a2c72dc65?from=singlemessage 1.国内几种常用坐标系说明 (1)名词解释 坐标系统:用于定位的系统,就跟 ...
- Linux(二):Linux的灵魂
上次说Linux的前世今生的时候,提了一句,就像学习java一样,我们有一个核心的准则 "万物皆对象" ,学习Linux,同样有基本准则,这也是Linux的最基本的特点,那就是&q ...
- 渐变颜色css设置
小说付费章节渐变颜色配置 position: absolute; top: 0; left: 0; width: 100%; height: 211px; transform: translateY( ...
- C数据结构:树和森林存储方式与遍历方式
文章目录 树的存储方式 双亲表示法 孩子链表表示法 孩子兄弟表示法(二叉树表示法) 树和二叉树的转换 森林和二叉树的转换 树和森林的遍历 树的遍历方式 森林的遍历方式 浅谈一下几个问题 为什么树没有中 ...
- pageoffice6提取word指定位置(数据区域)的值
在实际的开发过程中,经常会遇到提取Word文档中指定位置的数据保存到数据库中的需求,PageOffice客户端控件即支持在线保存Word文件,也支持Word文档中的指定位置的数据或所有的数据提交到服务 ...
- 基于uniapp+vue3自定义增强版table表格组件「兼容H5+小程序+App端」
vue3+uniapp多端自定义table组件|uniapp加强版综合表格组件 uv3-table:一款基于uniapp+vue3跨端自定义手机端增强版表格组件.支持固定表头/列.边框.斑马纹.单选/ ...