在平台总线驱动模型中资源和驱动已经从逻辑上和代码组织上进行了分离,但每次调整资源还是会涉及到内核,所以现在更加流行的是设备树方式。设备树的好处是通过独立于内核存在,这样如果设备上外设功能启用与否以及位置变动的话很多时候不用修改与编译内核,只要重新处理设备树文件即可。
设备树代码(或者都算不上代码)只是一些树状的数据,有点像JSON。
设备树源文件有两种尾缀,分别是 dts 以及 dtsi。dtsi 常规意义上定义 SoC 级别的硬件 信息,而 dts 常规意义上定义 board 级别的硬件信息。dts 通过#include 的方式包含进 dtsi 文件
通常每个系列的芯片厂家都会编写好后缀为 .dtsi 的设备树文件,里面把芯片基本上的功能资源都定义了。而对于某个具体的电路板来说,只要编写后缀为 .dts 的文件,在其中引入前述的 .dtsi 文件,然后在 .dts 文件中选择性启用 .dtsi 中已经定义好的并且电路中需要用到的功能。当然在 .dts 文件中也可以自定义新的功能。
.dts 文件最终可以编译为 .dtb 文件,系统在启动的时候会通过Bootloader将该文件传递给内核,内核就会解析取用其中的资源并与驱动进行匹配。如果资源需要调整,通常只需要调整 .dts 文件生成新的 .dtb 文件即可。
设备树的概念:
设备树只是用来给内核里的驱动程序, 指定硬件的信息 。比如 LED 驱动,在内核的驱动程序里去操作寄存器,但是操作哪一个引脚?这由设备树指定。
设备树:
怎么描述这棵树?
我们需要编写设备树文件 (dts: device tree source) ,它需要编译为 dtb(device tree blob)文件,内核使用的是 dtb 文件。
设备树在系统中的编译流程:
一个单板启动时, u-boot 先运行,它的作用是启动内核。 U-boot 会把内核 和设备树文件都读入内存,然后启动内核。在启动内核时会把设备树在内存中的地址告诉内核。
当 U-Boot 通过 ARM 通用寄存器 r2 将设备树 dtb 文件 memory 地址传递给 kernel 之 后,kernel 会将平滑的 FDT 文件解析成 EDT 文件,kernel 将 dtb 进行加工处理、解析成device_node,再将 device_node 转换成各色的 device 如 platform_device 供驱动代码使用。
将创建好的platform_device绑定到platform_bus上
将device 增加进 linux 系统
设备树文件不需要我们从零写出来,内核支持了某款芯片比如 imx6ull,在内核的arch/arm/boot/dts 目录下就有了能用的设备树模板,一般命名为 xxxx.dtsi。“i”表示“include”,被别的文件引用的。
我们使用某款芯片制作出了自己的单板,所用资源跟 xxxx.dtsi 是大部分相同,小部分不同,所以需要引脚 xxxx.dtsi 并修改。 dtsi 文件跟 dts 文件的语法是完全一样的
编译、更换设备树
我们一般不会从零写 dts 文件,而是修改。程序员水平有高有低,怎么知道改得对不对?需要编译一下。并且内核直接使用 dts 文件的话,就太低效了,它也需要使用二进制格式的 dtb 文件。
kernel 编译时使用 scritpts 下的 dtc 工具(宿主机同样也有 dtc 工具),根据 arch/arm/boot/dts/Makefile 中的规则,将设备树源码编译成 dtb 格式的 ABI 文件。
在内核中直接 make
设置 ARCH 、 CROSS_COMPILE 、 PATH 这三个环境变量后,进入 ubuntu 上板子内核源码的目录,执行如下命令即可编译 dtb 文件:make dtbs V=1
内核对设备树的处理
从源代码文件 dts 文件开始,设备树的处理过程为:
- dts 在 PC 机上被编译为 dtb 文件;
- u-boot 把 dtb 文件传给内核;
- 内核解析 dtb 文件,把每一个节点都转换为 device_node 结构体;
- 对于某些 device_node 结构体,会被转换为 platform_device 结构体。
哪些设备树节点会被转换为 platform_device
a)根节点下含有compatile 属性的子节点
b)含有特定compatile 属性的节点的子节点
如果一个节点的 compatile 属性,它的值是这 4 者之一:"simple-bus","simple-mfd","isa","arm,amba-bus", 那 么 它 的 子结点 ( 需 含 compatile 属性)也可以转换为 platform_device。
c)总线 I2C、SPI 节点下的子节点:不转换为 platform_device
某个总线下到子节点,应该交给对应的总线驱动程序来处理, 它们不应该被 转换为 platform_device。
怎么转换为 platform_device
内核处理设备树的函数调用过程,这里不去分析;我们只需要得到如下结论:
◼ platform_device 中含有 resource 数组 , 它来自 device_node 的 reg, interrupts 属性 ;
◼ platform_device.dev.of_node 指向 device_node, 可以通过它获得其他属性
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 和 p latform_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。
3 接下来比较: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 的私有数据。
4 最后比较
⚫ 比较:platform_device.name 和 platform_driver.driver.name
platform_driver.id_table 可能为空, 这 时 可 以 根 据 platform_driver.driver.name 来 寻 找 同 名 的 platform_device。
怎么修改设备树文件
一个写得好的驱动程序 , 它会尽量确定所用资源。只把不能确定的资源留给 设备树, 让设备树来指定。根据原理图确定 " 驱动程序无法确定的硬件资源 ", 再在设备树文件中填写对应内容。那么 , 所填写内容的格式是什么 ?
使用芯片厂家提供的工具
有些芯片,厂家提供了对应的设备树生成工具,可以选择某个引脚用于某些 功能,就可以自动生成设备树节点。 再把这些节点复制到内核的设备树文件里即可。
看绑定文档
内核文档 Documentation/devicetree/bindings/ 做得好的厂家也会提供设备树的说明文档
参考同类型单板的设备树文件
网上搜索
实在没办法时, 只能去研究驱动源码
原文链接:https://blog.csdn.net/Naisu_kun/article/details/130860190