设备树(DT)是易于阅读的硬件描述文件,它采用JSON式的格式化风格,在这种简单的树形结构中,
设备表示为带有属性的节点。属性可以为空(只有键,用来描述布尔值),也可以是键值对,其中的值可以是任意的字节流。

<>1 设备树机制

将选项CONFIG_OF设置为Y即可在内核中启用DT。要在驱动程序中调用DT API,必须添加以下头文件
#include <linux/of.h> #include <linux/of_device.h>
设备树支持一些数据类型如下:

* 文本字符串用双引号表示。可以使用逗号来创建字符串列表
* 单元格是由尖括号分隔的无符号32位整数
* 布尔类型不过是空属性,其值是true或false取决于属性存在于否
如: node_lable:nodename@reg{ srting-property="a string"; //一个字符串
string-list="read","write"; //字符串列表 onw-int-property=<197>; //一个整型数字
int-list-property=<0xbeef 123 0xabcd 4>; //整型列表 byte-array-property=[0x01 0x02
0x35 0x47]; //字节数组 bool-property; //布尔数据 mixed-list-property="a string",<oxabcd
45>,<35>,[0x01 0x23 0x45]; //混合类型 };
命名约定
每个节点都必须由 <名字>[@<地址>] 形式的名称,其中 <名字>
是一个字符串,其最多可以是31个字符,[@<地址>]是可选的,具体取决于节点代表的设备是否为可寻址。<地址>是用来访问设备的主要地址。设备命名的一个例子如下:
i2c@021a0000{ compatible = "fls,imx6q-i2c","fls,ima21-i2c"; reg = <0x021a0000
0x4000>; ... }
别名、标签和phandle
node_lable:nodename@reg{ ... }; gpio1:gpio@0209c000{ ... }; aliases{ ... gpio1
= &gpio1; ... };

标签不过是标记节点的方法,可以用唯一的名称来标识节点。DT编译器将该名称转换为唯一的32位值。在上面的例子中,gpio1和node_label都是标签。之后可以用标签来引用节点,因为标签对于节点是唯一的。
指针句柄(point
handle,简写为phandle)是与节点相关联的32位值,用于唯一标识该结点,以便可以从另一个结点的属性引用该节点。标签用于一个指向节点的指针,使用&mylabel可以指向标签为mylabel的节点,比如gpio1
= &gpio1。&gpio1被转换为phandle,以便引用gpio1节点。

为了在查找节点时不遍历整棵树,引入了别名的概念。在DT中,别名节点可以看做是快速查找表,可以使用函数find_node_by_alias()来查找指定别名的节点。别名不是直接在DT源中使用,而是由Linux内核来引用。
DT编译器

DT有两种形式:文本形式(代表源,也称作DTS)和二进制块形式(代表编译后的DT),也称作DTB。源文件的拓展名是.dts。.dtsi是SOC级定义,.dts文件代表开发板定义。就像源文件(.c)和包含头文件(.h)一样,应该把.dtsi作为头文件包含在.dts文件中。而二进制文件使用.dtb拓展名。

<>2 表示和寻址设备

每个设备在DT中至少有一个节点。某些属性对于设备是通用的,这些属性是reg、#address-cells和#size-cells,它们的用途是在其所在总线上进行设备寻址。主要的寻址属性是reg,其含义取决于设备所在的总线。size-cells和address-cells的前缀#可以翻译为length。
每个可寻址设备都具有reg属性,该属性是reg =
,…形式的元组列表,其中每个元组代表设备的地址范围。#size-cells(长度单元)指示使用多少个32位单元来表示size0,如果与大小无关,则可以是0。
#address-cells(地址单元)指示用多少个32位单元来表示address0 。
可寻址设备继承它们父节点的#size-cells和#address-cells,其指定的#size-cells和#address-cells影响子设备。
SPI和I2C寻址

SPI和I2C设备都属于非内存映射设备,因为它们的地址对CPU不可访问。而总线控制器驱动程序将代表CPU执行间接访问。每个I2C/SPI设备都表示为在I2C/SPI总线节点上的子节点。对于非存储映射的设备,#size-cells表示为0,寻址元组中的size元素为空。这意味着这种设备的reg属性总是只有一个单元:
&i2c3{ ... temperature-sensor@49{ ... reg=<0x49>; ... }; pcf8523:rtc@68{ ...
reg = <0x68>; ... }; ... }; &ecspi{ ... cs-gpios = <&gpio4 17 0>,<&gpio5 17
0>,<&gpio6 17 0>; ... ad7606r8_0:ad7606r8@1{ ... reg = <1>; ... }; ... };
reg属性只是一个保存地址值的单元格,I2C设备的reg属性用于指定总线上设备的地址。对于SPI,reg表示从控制器节点 所具有的芯片选择列表中
分配给设备的芯片选择线的索引。例如对于ad7606r8 ADC,芯片选择索引是1,对应于cs-gpios中的<&gpio5 17
0>,cs-gpios是控制器节点的芯片选择列表。
平台设备寻址

下面介绍的内存映射设备,其内存可由CPU访问。在这里,reg属性仍然定义设备的地址,这是可以访问设备的内存区域列表。每个区域用单元格元组表示,其中第一个单元格是内存区域的基地址,第二个单元格是该区域的大小。每个元组代表设备使用的地址范围。
这种设备应该在具有特殊值compatible= "simple-bus"的节点内声明,这意味着简单的内存映射总线,没有特定的处理和驱动程序:
soc{ #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus";
aips-bus@02000000{ ... #address-cells = <1>; #size-cells = <1>; reg =
<0x02000000 0x100000>; ... spba-bus@02000000{ ... #address-cells = <1>;
#size-cells = <1>; reg = <0x02000000 0x400000>; ... ecspi:ecspi@02008000 { ...
#address-cells = <1>; #size-cells = <0>; reg = <0x02008000 0x400>; ... }; ...
}; ... }; ... };
父结点compatible=
“simple-bus”,其在节点将被注册为平台设备。设置#size-cells=<0>也能够看到SPI总线控制器怎样改变其子节点的寻址方式的。从内核设备树文档可以查找所有
绑定信息:Document/devicetree/binding/。

<>3 处理资源

当驱动程序期望某种类型的资源列表时,由于编写开发板设备树的人通常不是写驱动程序的人,因此不能保证该列表是以驱动程序期望的方式排序。例如,驱动程序可能期望其设备节点具有2条IRQ线路,一条用于索引0处的Tx事件,另一条用于索引1处的Rx。如果这种顺序得不到满足,驱动就会发生异常行为。为了避免这种不匹配,引入了命名资源的概念,它由定义资源列表和命名组成,因此无论索引什么,给定的名称总将与资源相匹配。
命名资源的相关属性如下:

* reg-names:reg属性中的内存区域列表
* clock-names:clocks属性中命名clocks
* interrupts: 标识设备的中断属性 ... interrupts = <0 50 0>; # interrupts = <中断类型 中断号
中断方式>;
* interrupt-names:为interrupts属性中的每个中断指定一个名称
* dma-names:用于dma属性
例如: fake_device{ ... reg=<0x4a06400 0x800>,<0x4a064800 0x200>,<0x4a064c00
0x200>; reg-name="config","ohci","ehci"; interrupters=<0 66
IRQ_TYPE_LEVEL_HIGH>,<0 67 IRQ_TYPE_LEVEL_HIGH>;
interrupter-names="ohci","ehci"; clocks=<&clks IMAX6QDL_CLK_UART_IPG>,<&clks
IMAX6QDL_CLK_UART_SERTAL>; clock-names="ipg","per"; dmas=<&sdma 25 4 0>,<&sdma
26 4 0>; dma-names="rx","tx"; };
驱动程序中提取每个命名资源代码如下所示:
/usr/src/linux-2.6.21.5/include/linux/ioport.h struct resource {
resource_size_t start; resource_size_t end; const char *name; unsigned long
flags; struct resource *parent, *sibling, *child; }; struct resource_list {
struct resource_list *next; struct resource *res; struct pci_dev *dev; };
struct resource *res1,*res2; res1 =
platform_get_resource_byname(pdev,IORESOURCE_MEM,"ohci"); res2 =
platform_get_resource_byname(pdev,IORESOURCE_MEM,"config"); struct dma_chan
*dma_chan_rx,*dma_chan_tx;
dma_chan_rx=dma_request_slave_channel(&pdev->dev,"rx");
dma_chan_tx=dma_request_slave_channel(&pdev->dev,"tx"); int txirq,rxirq; txirq
= platform_get_irq_byname(pdev,"ohci"); rxirq =
platform_get_irq_byname(pdev,"ehci"); struct *clk_ipg,*clk_per; clk_ipg =
devm_clk_get(&pdev->dev,"ipg"); clk_per = devm_clk_get(&pdev->dev,"per");
这样,就可以确保把正确的名字映射到正确的资源上,而不用再使用索引了。
提取特定应用数据

特定应用数据时公共属性之外的数据(既不是资源,也不是IO、调度器等),可以分配给设备的任意属性和子节点,这样的属性名称通常以制造商代码作前缀。它们可以是任何字符串、布尔值或整型值。
文本字符串
下面是一个string属性:
string-property = "a string";
驱动程序使用of_property_read_string()读取字符串值,其原型定义如下:
int of_property_read_string(const struct device_node *np,const char
*propname,const char **out_string);
示例:
const char *my_string = NULL;
of_property_read_string(pdev->dev.of_node,"string-property",&my_string);
单元格和无符号的32位整数
下面是unsigned int属性:
one-int-property=<197>; int-list-property = <1350000 0x54dae47 1250000
1200000>;
应该使用of_property_read_u32()读取单元格值。其原型定义如下:
int of_property_read_u32(const struct device_node *np,const char *propname,u32
*out_value);
驱动中使用:
unsigned int number;
of_property_read_u32(pdev->dev.of_node,"one-cell-property",&number);
可以使用of_property_read_u32_array()读取单元格列表,其原型如下:
int of_property_read_u32_array(const struct device_node *np, const char
*propname, u32 *out_values, size_t sz);
sz是要读取数组元素的数量。比如:
unsigned int cells_array[4];
of_property_read_u32_array(pdev->dev.of_node,"int-list-property",cells_array,4);
布尔
使用of_property_read_bool()读取布尔属性。属性的名称在函数的第二个参数中给出:
bool my_bool = of_property_read_bool(pdev->dev.of_node,"bool-property");
if(my_bool) { /* 布尔值为真 */ } else{ /* 布尔值为假 */ }
提取并分析子节点
使用for_each_child_of_node()遍历指定节点的子节点:
struct device_node *np = pdev->dev.of_node; struct device_node *sub_np;
for_each_child_of_node(np,sub_np) { ... }
<>4 平台驱动程序与DT

平台驱动程序也使用DT,也就是说,这是现在推荐的处理平台设备的方法,不在需要使用开发板文件,甚至不需要在设备的属性更改时重新编译内核。
OF匹配风格

OF匹配风格是平台核心执行的第一种匹配机制,以匹配设备及其驱动程序。它使用设备树的compatible属性来匹配of_match_table中的设备项,设备项是struct
driver子结构的一个字段。每个设备节点都有compatible属性,它是字符串或字符串列表。任何驱动程序只要声明compatible属性中列出的字符串之一,就将触发匹配,并看到probe函数执行。
DT匹配项在内核中被描述为struct of_device_id结构的实例,该结构定义在liunx/mod_devicetable.h中,如下所示:
struct of_device_id{ ... char compatible[128]; const void *data; };
* compatible:这是用来匹配DT设备节点兼容属性的字符串字符串,它们必须完全相同才可以匹配
* data:这可以指向任何结构,这个结构可以用作每个设备类型的配置数据。
of_match_table是指针,可以传递struct of_device_id数组,使驱动程序兼容多个设备: static const struct
of_device_id imx_uart_dt_ids[] = { { .compatible = "fsl,imx6q-uart",}, {
.compatible = "fsl,imx1-uart",}, { .compatible = "fsl,imx21-uart",}, .... };
填充了of_device_id 数组,就必须把它传递到平台驱动程序的of_match_table字段:
static struct platform_driver serial_imx_driver = { ... .driver = { .name =
"imax-uart", .of_match_table = imx_uart_dt_ids, ... }; };
这一步,只有驱动程序知道of_device_id数组,要使内核也或得通知(这样可以把of_device_id
数组存储到平台内核维护的设备列表中),该数组必须用MODULE_DEVICE_TABLE(设备类型,设备表)注册:
MODULE_DEVICE_TABLE(of,imx_uart_dt_ids);
这样,驱动程序就兼容DT,接下来声明与驱动程序兼容的设备:
uart1:serial@02020000{ compatible = "fsl,imx6q-uart","fls,imx21-uart"; reg =
<0x02020000 0x4000>; ... };
这里提供了两个兼容的字符串,如果第一个与任何驱动程序都不匹配,则平台核心将执行第二个匹配。
当发生匹配时,将以struct platform_device 结构作为参数调用驱动程序的probe函数。
处理非设备树平台
使用CONFIG_OF选项在内核中启用DT支持。如果内核没有启用DT支持,则可能会希望避免使用DT
API。其实现的方法是检查CONFIG_OF是否设置。过去常常这样做:
#ifdef CONFIG_OF static const struct of_device_id imx_uart_dt_ids[] = { {
.compatible = "fsl,imx6q-uart",}, { .compatible = "fsl,imx1-uart",}, {
.compatible = "fsl,imx21-uart",}, .... }; .... #endif
这不是唯一的选择,还可以使用of_match_ptr宏,当OF被禁用时它简单地返回NULL,查看源代码,在include/linux/of.h里面。
#ifdef CONFIG_OF ... #define of_match_ptr(_ptr) (_ptr) ... #else /* CONFIG_OF
*/ ... #define of_match_ptr(_ptr) NULL ... #endif /* CONFIG_OF */
平台数据与DT

如果驱动程序需要平台数据,则应该检查dev.platfrom_data指针。非空值表示驱动程序已经在开发板配置文件中以旧的方法实例化,并且DT不会处理它。对于从DT实例化的驱动程序,dev.platform_data将为NULL,平台设备将在DT项上获得一个指针,该指针对应于dev.of_node指针中的设备,可以从该指针中提取资源并使用OF
API分析和提取应用数据

设备树结构

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信