文章目录

  • 1 内核对设备树的处理
    • 1.1 dtb 中每一个节点都被转换为 device_node 结构体
    • 1.2 哪些设备树节点会被转换为 platform_device
    • 1.3 怎么转换为 platform_device
  • 2 platform_device 如何与platform_driver 配对
  • 3 内核里操作设备树的常用函数
    • 3.1 内核中设备树相关的头文件介绍
      • 3.1.1 处理 DTB
      • 3.1.2 处理 device_node
      • 3.1.3 处理 platform_device
    • 3.2 platform_device 相关的函数
      • 3.2.1 of_find_device_by_node
      • 3.2.2 platform_get_resource
    • 3.3 有些节点不会生成 platform_device,怎么访问它们
      • 3.3.1 找到节点
      • 3.3.2 找到属性
      • 3.3.3 获取属性的值
  • 4 怎么修改设备树文件

1 内核对设备树的处理

从源代码文件 dts 文件开始,设备树的处理过程为:
① dts 在 PC 机上被编译为 dtb 文件;
② u-boot 把 dtb 文件传给内核;
③ 内核解析 dtb 文件,把每一个节点都转换为 device_node 结构体;
④ 对于某些 device_node 结构体,会被转换为 platform_device 结构体。

1.1 dtb 中每一个节点都被转换为 device_node 结构体


根节点被保存在全局变量 of_root 中,从 of_root 开始可以访问到任意节点。

1.2 哪些设备树节点会被转换为 platform_device

A. 根节点下含有 compatile 属性的子节点。
B. 含有特定 compatile 属性的节点的子节点:
如 果 一 个 节 点 的 compatile 属 性 , 它 的 值 是 这 4 者之一: “simple-bus”,“simplemfd”,“isa”,“arm,amba-bus”, 那么它的子结点(需含 compatile 属性)也可以转换为platform_device。

C. 总线 I2C、SPI 节点下的子节点:不转换为 platform_device某个总线下到子节点,应该交给对应的总线驱动程序来处理, 它们不应该被转换为 platform_device。

对于如下设备树文件:

/ {mytest {compatile = "mytest", "simple-bus";mytest@0 {compatile = "mytest_0";};};i2c {compatile = "samsung,i2c";at24c02 {compatile = "at24c02"; };};spi {compatile = "samsung,spi"; flash@0 {compatible = "winbond,w25q32dw";spi-max-frequency = <25000000>;reg = <0>;};};};

比如以下的节点中:

  • /mytest 会被转换为 platform_device, 因为它兼容"simple-bus";它的子节点/mytest/mytest@0 也会被转换为 platform_device
  • /i2c 节点一般表示 i2c 控制器, 它会被转换为 platform_device, 在内核中有对应的 platform_driver;
  • /i2c/at24c02 节点不会被转换为 platform_device, 它被如何处理完全由父节点的 platform_driver决定, 一般是被创建为一个 i2c_client。
  • 类似的也有/spi 节点, 它一般也是用来表示 SPI 控制器, 它会被转换为 platform_device, 在内核中
    有对应的 platform_driver;
  • /spi/flash@0 节点不会被转换为 platform_device, 它被如何处理完全由父节点的 platform_driver决定, 一般是被创建为一个 spi_device。

1.3 怎么转换为 platform_device

内核处理设备树的函数调用过程,这里不去分析;我们只需要得到如下结论:
A. platform_device 中含有 resource 数组, 它来自 device_node 的 reg, interrupts 属性;
B. platform_device.dev.of_node 指向 device_node, 可以通过它获得其他属性。


2 platform_device 如何与platform_driver 配对

从设备树转换得来的 platform_device 会被注册进内核里,以后当我们每注册一个 platform_driver时,它们就会两两确定能否配对,如果能配对成功就调用 platform_driver 的 probe 函数。套路是一样的。我们需要将前面讲过的“匹配规则”再完善一下:
先贴源码:

1. 最先比较:是否强制选择某个 driver
比较 platform_device. driver_override 和platform_driver.driver.name,可以设置 platform_device 的 driver_override,强制选择某个 platform_driver。

2.然后比较:设备树信息
比较:platform_device. dev.of_node 和 platform_driver.driver.of_match_table。
由设备树节点转换得来的 platform_device 中,含有一个结构体:of_node。
它的类型如下:

如果一个 platform_driver 支持设备树,它的platform_driver.driver.of_match_table 是一个数组,类型如下:

使用设备树信息来判断 dev 和 drv 是否配对时,首先,如果 of_match_table 中含有 compatible 值,就跟 dev 的 compatile 属性比较,若一致则成功,否则返回失败;

其次,如果 of_match_table 中含有 type 值,就跟 dev 的 device_type 属性比较,若一致则成功,否则返回失败;

最后,如果 of_match_table 中含有 name 值,就跟 dev 的 name 属性比较,若一致则成功,否则返回失败。

而设备树中建议不再使用 devcie_type 和 name 属性,所以基本上只使用设备节点的 compatible 属性来寻找匹配的 platform_driver。

接下来比较:platform_device_id
比较 platform_device. name 和 platform_driver.id_table[i].name,id_table 中可能有多项。

platform_driver.id_table 是“platform_device_id”指针,表示该 drv 支持若干个 device,它里面列出了各个 device 的{.name, .driver_data},其中的“name”表示该 drv 支持的设备的名字,driver_data是些提供给该 device 的私有数据。

最后比较:platform_device.name 和 platform_driver.driver.name:
platform_driver.id_table 可能为空,这时可以根据 platform_driver.driver.name 来寻找同名的 platform_device。

一个图概括所有的配对过程:
概括出了这个图:

没有转换为 platform_device 的节点,如何使用?
任意驱动程序里,都可以直接访问设备树。


3 内核里操作设备树的常用函数

内核源码中 include/linux/目录下有很多 of 开头的头文件,of 表示“open firmware”即开放固件。

3.1 内核中设备树相关的头文件介绍

设备树的处理过程是:dtb -> device_node -> platform_device。

3.1.1 处理 DTB

of_fdt.h // dtb 文件的相关操作函数, 我们一般用不到,
// 因为 dtb 文件在内核中已经被转换为 device_node 树(它更易于使用)

3.1.2 处理 device_node

of.h // 提供设备树的一般处理函数,
// 比如 of_property_read_u32(读取某个属性的 u32 值),
// of_get_child_count(获取某个 device_node 的子节点数)
of_address.h // 地址相关的函数,
// 比如 of_get_address(获得 reg 属性中的 addr, size 值)
// of_match_device (从 matches 数组中取出与当前设备最匹配的一项)
of_dma.h // 设备树中 DMA 相关属性的函数
of_gpio.h // GPIO 相关的函数
of_graph.h // GPU 相关驱动中用到的函数, 从设备树中获得 GPU 信息
of_iommu.h // 很少用到
of_irq.h // 中断相关的函数
of_mdio.h // MDIO (Ethernet PHY) API
of_net.h // OF helpers for network devices.
of_pci.h // PCI 相关函数
of_pdt.h // 很少用到
of_reserved_mem.h // reserved_mem 的相关函数

3.1.3 处理 platform_device

of_platform.h // 把 device_node 转换为 platform_device 时用到的函数, // 比如 of_device_alloc(根据 device_node 分配设置 platform_device), // of_find_device_by_node (根据 device_node 查找到 platform_device),// of_platform_bus_probe (处理 device_node 及它的子节点)
of_device.h // 设备相关的函数, 比如 of_match_device

3.2 platform_device 相关的函数

of_platform.h 中声明了很多函数,但是作为驱动开发者,我们只使用其中的 1、2 个。其他的都是给内核自己使用的,内核使用它们来处理设备树,转换得到 platform_device。

3.2.1 of_find_device_by_node

函数原型为:

extern struct platform_device *of_find_device_by_node(struct device_node *np);

设备树中的每一个节点,在内核里都有一个 device_node;你可以使用 device_node 去找到对应的platform_device。

3.2.2 platform_get_resource

这个函数跟设备树没什么关系,但是设备树中的节点被转换为 platform_device 后,设备树中的 reg 属性、interrupts 属性也会被转换为“resource”。

这时,你可以使用这个函数取出这些资源。

函数原型为:

/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type // 取哪类资源?IORESOURCE_MEM、IORESOURCE_REG
* // IORESOURCE_IRQ 等
* @num: resource index // 这类资源中的哪一个?
*/
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);

对于设备树节点中的 reg 属性,它对应 IORESOURCE_MEM 类型的资源;
对于设备树节点中的 interrupts 属性,它对应IORESOURCE_IRQ 类型的资源。

3.3 有些节点不会生成 platform_device,怎么访问它们

内核会把 dtb 文件解析出一系列的 device_node 结构体,我们可以直接访问这些 device_node。
内核源码 incldue/linux/of.h 中声明了 device_node 和属性 property 的操作函数,device_node 和property 的结构体定义如下:

3.3.1 找到节点

a. of_find_node_by_path
根据路径找到节点,比如“/”就对应根节点,“/memory”对应 memory 节点。
函数原型:

static inline struct device_node *of_find_node_by_path(const char *path);

b. of_find_node_by_name
根据名字找到节点,节点如果定义了 name 属性,那我们可以根据名字找到它。
函数原型:

extern struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);

参数 from 表示从哪一个节点开始寻找,传入 NULL 表示从根节点开始寻找。

但是在设备树的官方规范中不建议使用“name”属性,所以这函数也不建议使用。

c. of_find_node_by_type
根据类型找到节点,节点如果定义了 device_type 属性,那我们可以根据类型找到它。
函数原型:

extern struct device_node *of_find_node_by_type(struct device_node *from,
const char *type);

参数 from 表示从哪一个节点开始寻找,传入 NULL 表示从根节点开始寻找。

但是在设备树的官方规范中不建议使用“device_type”属性,所以这函数也不建议使用。

d. of_find_compatible_node
根据 compatible 找到节点,节点如果定义了 compatible 属性,那我们可以根据 compatible 属性找到它。
函数原型:

extern struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compat);

参数 from 表示从哪一个节点开始寻找,传入 NULL 表示从根节点开始寻找。
参数 compat 是一个字符串,用来指定 compatible 属性的值;
参数 type 是一个字符串,用来指定 device_type 属性的值,可以传入 NULL。

e. of_find_node_by_phandle
根据 phandle 找到节点。
dts 文件被编译为 dtb 文件时,每一个节点都有一个数字 ID,这些数字 ID 彼此不同。可以使用数字 ID来找到 device_node。这些数字 ID 就是 phandle。
函数原型:

extern struct device_node *of_find_node_by_phandle(phandle handle);

f. of_get_parent
找到 device_node 的父节点。
函数原型:

extern struct device_node *of_get_parent(const struct device_node *node);

g. of_get_next_parent
这个函数名比较奇怪,怎么可能有“next parent”?
它实际上也是找到 device_node 的父节点,跟 of_get_parent 的返回结果是一样的。
差别在于它多调用下列函数,把 node 节点的引用计数减少了 1。这意味着调用 of_get_next_parent 之
后,你不再需要调用 of_node_put 释放 node 节点。
of_node_put(node);
函数原型:

extern struct device_node *of_get_next_parent(struct device_node *node);

h. of_get_next_child
取出下一个子节点。
函数原型:

extern struct device_node *of_get_next_child(const struct device_node *node,struct device_node *prev);

参数 node 表示父节点;
prev 表示上一个子节点,设为 NULL 时表示想找到第 1 个子节点。

不断调用 of_get_next_child 时,不断更新 pre 参数,就可以得到所有的子节点。

i. of_get_next_available_child
取出下一个“可用”的子节点,有些节点的 status 是“disabled”,那就会跳过这些节点。
函数原型:

struct device_node *of_get_next_available_child(const struct device_node *node,
struct device_node *prev);

参数 node 表示父节点;
prev 表示上一个子节点,设为 NULL 时表示想找到第 1 个子节点。

j. of_get_child_by_name
根据名字取出子节点。
函数原型:

extern struct device_node *of_get_child_by_name(const struct device_node *node, const char *name);

参数 node 表示父节点;
name 表示子节点的名字。

3.3.2 找到属性

内核源码 incldue/linux/of.h 中声明了 device_node 的操作函数,当然也包括属性的操作函数。

a. of_find_property
找到节点中的属性。
函数原型:

extern struct property *of_find_property(const struct device_node *np,const char *name,int *lenp);

参数 np 表示节点,我们要在这个节点中找到名为 name 的属性。

lenp 用来保存这个属性的长度,即它的值的长度。

在设备树中,节点大概是这样:
xxx_node {
xxx_pp_name = “hello”;
};
上述节点中,“xxx_pp_name”就是属性的名字,值的长度是 6。

3.3.3 获取属性的值

a. of_get_property
根据名字找到节点的属性,并且返回它的值。
函数原型:

/*
* Find a property with a given name for a given node
* and return the value.
*/
const void *of_get_property(const struct device_node *np, const char *name,int *lenp)

参数 np 表示节点,我们要在这个节点中找到名为 name 的属性,然后返回它的值。

lenp 用来保存这个属性的长度,即它的值的长度。

b. of_property_count_elems_of_size
根据名字找到节点的属性,确定它的值有多少个元素(elem)。
函数原型:

* of_property_count_elems_of_size - Count the number of elements in a property
*
* @np: device node from which the property value is to be read.
* @propname: name of the property to be searched.
* @elem_size: size of the individual element
*
* Search for a property in a device node and count the number of elements of
* size elem_size in it. Returns number of elements on sucess, -EINVAL if the
* property does not exist or its length does not match a multiple of elem_size
* and -ENODATA if the property does not have a value.
*/
int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size)

参数 np 表示节点,我们要在这个节点中找到名为 propname 的属性,然后返回下列结果:

return prop->length / elem_size;

在设备树中,节点大概是这样:

xxx_node {xxx_pp_name = <0x50000000 1024> <0x60000000 2048>;
};

调用 of_property_count_elems_of_size(np, “xxx_pp_name”, 8)时,返回值是 2;
调用 of_property_count_elems_of_size(np, “xxx_pp_name”, 4)时,返回值是 4。

c. 读整数 u32/u64
函数原型为:

static inline int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value);
extern int of_property_read_u64(const struct device_node *np,
const char *propname, u64 *out_value);

在设备树中,节点大概是这样:

xxx_node {name1 = <0x50000000>;name2 = <0x50000000 0x60000000>;
};

调用 of_property_read_u32 (np, “name1”, &val)时,val 将得到值 0x50000000;
调用 of_property_read_u64 (np, “name2”, &val)时,val 将得到值 0x0x6000000050000000。

d. 读某个整数 u32/u64
函数原型为:

extern int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value);

在设备树中,节点大概是这样:

xxx_node {name2 = <0x50000000 0x60000000>;
};

调用 of_property_read_u32 (np, “name2”, 1, &val)时,val 将得到值 0x0x60000000。

e. 读数组
函数原型为:

int of_property_read_variable_u8_array(const struct device_node *np,
const char *propname, u8 *out_values,
size_t sz_min, size_t sz_max);
int of_property_read_variable_u16_array(const struct device_node *np,
const char *propname, u16 *out_values,
size_t sz_min, size_t sz_max);
int of_property_read_variable_u32_array(const struct device_node *np,const char *propname, u32 *out_values,size_t sz_min, size_t sz_max);
int of_property_read_variable_u64_array(const struct device_node *np,const char *propname, u64 *out_values,size_t sz_min, size_t sz_max);

在设备树中,节点大概是这样:

xxx_node {name2 = <0x50000012 0x60000034>;
};

上述例子中属性 name2 的值,长度为 8。
调用 of_property_read_variable_u8_array (np, “name2”, out_values, 1, 10)时,out_values 中将会保存这 8 个字节: 0x12,0x00,0x00,0x50,0x34,0x00,0x00,0x60。
调用 of_property_read_variable_u16_array (np, “name2”, out_values, 1, 10)时,out_values中将会保存这 4 个 16 位数值: 0x0012, 0x5000,0x0034,0x6000。

总之,这些函数要么能取到全部的数值,要么一个数值都取不到;如果值的长度在 sz_min 和 sz_max 之间,就返回全部的数值;否则一个数值都不返回。

f. 读字符串
函数原型为:

int of_property_read_string(const struct device_node *np, const char *propname,const char **out_string);

返回节点 np 的属性(名为 propname)的值,(*out_string)指向这个值,把它当作字符串。


4 怎么修改设备树文件

一个写得好的驱动程序, 它会尽量确定所用资源。
只把不能确定的资源留给设备树, 让设备树来指定。

据原理图确定"驱动程序无法确定的硬件资源", 再在设备树文件中填写对应内容。那么, 所填写内容的格式是什么?

  1. 使用芯片厂家提供的工具。
    有些芯片,厂家提供了对应的设备树生成工具,可以选择某个引脚用于某些功能,就可以自动生成设备树节点。你再把这些节点复制到内核的设备树文件里即可。
  2. 看绑定文档,内核文档 Documentation/devicetree/bindings/
    做得好的厂家也会提供设备树的说明文档。
  3. 参考同类型单板的设备树文件。
  4. 网上搜索。
  5. 实在没办法时, 只能去研究驱动源码。

Linux内核对设备树的处理相关推荐

  1. 嵌入式(iMX6Q)TFTP加载 Linux 内核与设备树NFS挂载根文件系统

    配置实现过程: 嵌入式(iMX6Q)TFTP加载 Linux 内核与设备树 注:bootm对应启动uImage,bootz对应启动zImage setenv bootcmd "tftp 0x ...

  2. linux内核ufs设备树,Linux内核初始化流程笔记

    Linux内核初始化流程笔记 分类: LINUX 作者:gfree.wind@http://www.doczj.com/doc/fc580419c1c708a1294a4409.html 博客:htt ...

  3. 探究 Linux 内核 dts 设备树定义文件

    树莓派开发文档中介绍了Linux内核设备树相关的知识 https://www.raspberrypi.org/documentation/configuration/device-tree.md dt ...

  4. 基于tiny4412的Linux内核移植 -- 设备树的展开【转】

    转自:https://www.cnblogs.com/pengdonglin137/p/5248114.html#_lab2_3_1 阅读目录(Content) 作者信息 平台简介 摘要 正文 一.根 ...

  5. 基于tiny4412的Linux内核移植 -- 设备树的展开

    http://blog.csdn.net/ermuzhi/article/details/9298541 摘要 在Linux引入设备树之后,将原来写在代码中的大量的硬件信息全部移到了设备树中,然后在L ...

  6. 零基础学Linux内核之设备驱动篇(8)_设备模型

    零基础学Linux内核系列文章目录 前置知识篇 1. 进程 2. 线程 进程间通信篇 1. IPC概述 2. 信号 3. 消息传递 4. 同步 5. 共享内存区 编译相关篇 1. GCC编译 2. 静 ...

  7. linux3.5 usb 设备树,嵌入式 PowerPC Linux 平台扁平设备树FDT解析

    ] [-O ] [-o output_filename] [-V output_version] input_filename input_format可以使用以下三个参数: dtb: 表示输入文件为 ...

  8. linux内核中kset是什么意思,Linux内核之设备驱动-底层数据结构kobject/kset

    Linux内核之设备驱动-底层数据结构kobject/kset kobject kobject是组成device.driver.bus.class的基本结构.如果把前者看成基类,则后者均为它的派生产物 ...

  9. Linux-使用uboot命令将Linux镜像和设备树文件下载到DRAM中

    转载地址:http://www.manongjc.com/detail/17-yrvrbxtziuxqryv.html 一.使用uboot中的tftp命令将Linux镜像和设备树文件下载到DRAM中 ...

最新文章

  1. shell 基本使用
  2. 《深入理解计算机系统》读书笔记四:操作系统的抽象
  3. POJ 3889 Fractal Streets(逼近模拟)
  4. 东北农业大学大学计算机基础作业答案,大学计算机基础实践教学改革的研究
  5. python数字排列组合去重_排列组合-生成集合的所有子集
  6. hive-04-Hive函数大全
  7. avalon2学习教程11数据联动
  8. 计算机关机的界面,修改计算机关机界面_生活与休闲
  9. 02 linux常用命令
  10. ES6学习笔记七(Set和Map)
  11. CNDS-markdowm使用方法(^ _ ^)
  12. COMSOL流体操作
  13. beyondCompare this license key has been revoked密钥被撤销
  14. Html5和Css3的基础标签及常用属性
  15. 高德地图API画圆形、高的地图删除圆形打点
  16. 多源传感器GNSS INS 视觉 LiDAR 组合导航与SLAM开源项目总结
  17. 中国自媒体行业竞争格局与运营盈利模式分析报告2022年
  18. 【墨子对战平台】还没连接上墨子推演服务器,再等1秒 解决办法
  19. 华为鸿蒙os状态栏,华为再推新版鸿蒙OS系统!UI外观设计大变样 多达19款机型可升级...
  20. Goroutine并发调度模型深度解析之手撸一个协程池

热门文章

  1. git提交后的代码在哪儿_别乱提交代码了,来围观下大厂的 Git 提交规范
  2. VCS-bilibili教程篇1-Simulation Basics
  3. 基于FPGA的FFT变换实现
  4. 使用Java connector消费ABAP系统的函数
  5. post请求与get请求的差别
  6. kvm 网络配置之nat、用户模式
  7. Java中windows路径转换成linux路径等工具类
  8. Linux 上的高可用中间件
  9. linux将bridge当做hub来用
  10. DockPanel Suite 开源WINFORM 窗体停靠面板控件