中山建设局网站,可以用wpf做网站吗,汕头建设吧 百度贴吧,深圳网站关键词优化Linux3.x 以后的版本才引入了设备树#xff0c;设备树用于描述一个硬件平台的板级细节。在早些的linux内核#xff0c;这些“硬件平台的板级细节”保存在linux 内核目录“/arch”#xff0c;以ARM 平台为例“硬件平台的板级细节”保存在“/arch/arm/plat-xxx”和“/arch/arm…Linux3.x 以后的版本才引入了设备树设备树用于描述一个硬件平台的板级细节。在早些的linux内核这些“硬件平台的板级细节”保存在linux 内核目录“/arch”以ARM 平台为例“硬件平台的板级细节”保存在“/arch/arm/plat-xxx”和“/arch/arm/mach-xxx”目录下。随着处理器数量的增多用于描述“硬件平台板级细节”的文件越来越多导致Linux 内核非常臃肿Linux 之父发现这个问题之后决定使用设备树解决这个问题。设备树简单、易用、可重用性强linux3.x 之后大多采用设备树编写驱动。
关于设备树的详细请参考https://www.devicetree.org/
设备树简介
设备树的作用就是描述一个硬件平台的硬件资源。这个“设备树”可以被bootloader(uboot) 传递到内核内核可以从设备树中获取硬件信息。 设备树描述硬件资源时有两个特点。
第一以“树状”结构描述硬件资源。例如本地总线为树的“主干”在设备树里面称为“根节点”挂载到本地总线的IIC 总线、SPI 总线、UART 总线为树的“枝干”在设备树里称为“根节点的子节点”IIC 总线下的IIC 设备不止一个这些“枝干”又可以再分。第二设备树可以像头文件.h 文件那样一个设备树文件引用另外一个设备树文件这样可以实现“代码”的重用。例如多个硬件平台都使用STM32MP1 作为主控芯片那么我们可以将STM32MP1 芯片的硬件资源写到一个单独的设备树文件里面一般使用“.dtsi”后缀其他设备树文件直接使用“# includexxx”引用即可。
DTS、DTC 和DTB 它们是文档中常见的几个缩写。
DTS 是指.dts 格式的文件是一种ASII 文本格式的设备树描述也是我们要编写的设备树源码一般一个.dts 文件对应一个硬件平台位于Linux 源码的“/arch/arm/boot/dts”目录下。DTC 是指编译设备树源码的工具一般情况下我们需要手动安装这个编译工具。DTB 是设备树源码编译生成的文件类似于我们C 语言中“.C”文件编译生成“.bin”文件。
设备树框架
上一小节简单了解了设备树的作用到现在为止我们还不知道“设备树”是究竟是个什么样子。
下面的内容将围绕着设备树源码来讲解设备树框架和基本语法。
列表1: 设备树(内核源码/arch/arm/boot/dts/stm32mp157a-basic.dts)
#include stm32mp157c.dtsi
#include stm32mp157cac-pinctrl.dtsi
#include stm32mp157c-m4-srm.dtsi
#include dt-bindings/input/input.h
#include dt-bindings/mfd/st,stpmic1.h/ {model Embedfire STM32MP157 Star LubanCat Robot S1 Board;compatible st,stm32mp157a-dk1, st,stm32mp157;aliases {ethernet0 ethernet0;serial0 uart4;serial1 usart1;serial2 usart2;serial3 usart3;};chosen {stdout-path serial0:115200n8;};memoryc0000000 {reg 0xc0000000 0x40000000;};/*-------------以下内容省略--------------*/fmc {pinctrl-names default, sleep;pinctrl-0 fmc_pins_a;pinctrl-1 fmc_sleep_pins_a;status okay;#address-cells 1;#size-cells 0;nand: nand0 {reg 0;nand-on-flash-bbt;#address-cells 1;#size-cells 1;};
};
/ {#address-cells 1;#size-cells 1;cpus {#address-cells 1;#size-cells 0;cpu0: cpu0 {compatible arm,cortex-a7;device_type cpu;reg 0;clocks rcc CK_MPU;clock-names cpu;operating-points-v2 cpu0_opp_table;nvmem-cells part_number_otp;nvmem-cell-names part_number;};
/*-------------以下内容省略--------------*/设备树源码分为三部分介绍如下 第1-5 行头文件。设备树是可以像C 语言那样使用“#include”引用“.h”后缀的头文件也可以引用设备树“.dtsi”后缀的头文件。stm32mp157c.dtsi 由ST 官方提供是一个stm32mp157 平台“共用”的设备树文件。 第11-23 行设备树节点。设备树给我们最直观的感受是它由一些嵌套的大括号“{}”组成每一个“{}”都是一个“节点”。“/ {⋯};”表示“根节点”每一个设备树只有一个根节点。如果打开“stm32mp157c.dtsi”文件可以发现它也有一个根节点虽然“stm32mp157a-basic.dts”引用了“stm32mp157c.dtsi”文件但这并不代表“stm32mp157a-basic.dts”设备树有两个根节点因为不同文件的根节点最终会合并为一个。在根节点内部的“aliases {⋯}”、“chosen{⋯}”、“memory {⋯}”等字符都是根节点的子节点。 第29-43 行设备树节点追加内容。第三部分的子节点比根节点下的子节点多了一个“”这表示该节点在向已经存在的子节点追加数据。这些“已经存在的节点”可能定义在“stm32mp157a-basic.dts”文件也可能定义在“stm32mp157a-basic.dts”文件所包含的设备树文件里。本代码中的“cpu0 {⋯}”、“clks {⋯}”、“fec1 {⋯}”等等追加的目标节点就是定义在“stm32mp157c.dtsi”中。
到目前为止我们知道设备树由一个根节点和众多子节点组成子节点也可以继续包含其他节点也就是子节点的子节点。设备树的组成很简单下面我们一起来看看节点的基本格式和节点属性。
节点基本格式
设备树中的每个节点都按照以下约定命名
列表3: 节点基本格式
node-nameunit-address{属性1 ⋯属性2 ⋯属性3 ⋯子节点⋯
};node-name 节点名称
节点格式中的node-name 用于指定节点的名称。它的长度为1 至31 个字符只能由如下字符组成
表节点名称
字符描述0-9数字a-z小写字母A-Z大写字母,英文逗号.英文句号_下划线•加号•减号
另外节点名应当使用大写或小写字母开头并且能够描述设备类别。
注意根节点没有节点名它直接使用“/”指代这是一个根节点。
unit-address
unit-address 其中的符号“”可以理解为是一个分割符“unit-address”用于指定“单元地址”它的值要和节点“reg”属性的第一个地址一致。如果节点没有“reg”属性值可以直接省略“unit-address”不过要注意这时要求同级别的设备树下相同级别的子节点节点名唯一, 从这个侧面也可以了解到同级别的子节点的节点名可以相同但是要求“单元地址”不同node-nameunit-address 的整体要求同级唯一。
节点标签
在stm32mp157c.dtsi 头文件中节点名“cpu”前面多了个“cpu0”, 这个“cpu0”就是我们所说的节点标签。通常节点标签是节点名的简写所以它的作用是当其它位置需要引用时可以使用节点标签来向该节点中追加内容。
节点路径
通过指定从根节点到所需节点的完整路径可以唯一地标识设备树中的节点不同层次的设备树节点名字可以相同同层次的设备树节点要唯一。这有点类似于我们Windows 上的文件一个路径唯一标识一个文件或文件夹不同目录下的文件文件名可以相同。
节点属性
在节点的“{}”中包含的内容是节点属性通常情况下一个节点包含多个属性信息这些属性信息就是要传递到内核的“板级硬件描述信息”驱动中会通过一些API 函数获取这些信息。
例如根节点“/”就有属性compatible “st,stm32mp157a-dk1”, “st,stm32mp157”。我们可以通过该属性了解到硬件设备相关的名字叫“stm32mp157a-dk1”设备所使用的的是“stm32mp157”这颗SOC。
我们编写设备树最主要的内容是编写节点的节点属性通常情况下一个节点代表一个设备设备有哪些属性、怎么编写这些属性、在驱动中怎么引用这些属性是我们后面讲解的重点这一小节只讲解设备节点有哪些可设置属性。有一些节点属性是所有节点共有的一些作用于特定的节点我们这里介绍那些共有的节点属性其他节点属性使用到时再详细介绍。
节点属性分为标准属性和自定义属性也就是说我们在设备树中可以根据自己的实际需要定义、添加设备属性。标准属性的属性名是固定的自定义属性名可按照要求自行定义。
compatible 属性
属性值类型字符串
列表4: compatible 属性 model Embedfire STM32MP157 Star LubanCat Robot S1 Board;compatible st,stm32mp157a-dk1, st,stm32mp157;aliases {ethernet0 ethernet0;serial0 uart4;serial1 usart1;serial2 usart2;serial3 usart3;
};compatible 属性值由一个或多个字符串组成有多个字符串时使用“,”分隔开。
设备树中的每一个代表了一个设备的节点都要有一个compatible 属性。compatible 是系统用来决定绑定到设备的设备驱动的关键。compatible 属性是用来查找节点的方法之一另外还可以通过节点名或节点路径查找指定节点。
例如系统初始化时会初始化platform 总线上的设备时根据设备节点”compatible”属性和驱动中of_match_table 对应的值匹配了就加载对应的驱动。
model 属性
属性值类型字符串
示例
列表5: model 属性
model Embedfire STM32MP157 Star LubanCat Robot S1 Board;model 属性用于指定设备的制造商和型号推荐使用“制造商, 型号”的格式当然也可以自定义本例子就没有使用这种格式。
status 属性
属性值类型字符串
示例
列表6: status 属性
/* External sound card */
sound: sound {status disabled;
};状态属性用于指示设备的“操作状态”通过status 可以去禁止设备或者启用设备可用的操作状态如下表。默认情况下不设置status 属性设备是使能的。
#address-cells 和#size-cells
属性值类型u32
示例
列表7: #address-cells 和#size-cells
soc {
#address-cells 1;
#size-cells 1;compatible simple-bus;interrupt-parent gpc;ranges;ocrams: sram900000 {compatible fsl,lpm-sram;reg 0x900000 0x4000;};
};#address-cells 和#size-cells 属性同时存在在设备树ocrams 结构中它们用在有子节点的设备节点节点用于设置子节点的“reg”属性的“书写格式”。
补充reg 属性值由一串数字组成如上图中的reg 0x900000 0x4000ret 属性的书写格式为reg cells cells cells cells cells cells⋯长度根据实际情况而定这些数据分为地址数据地址字段长度数据大小字段。
#address-cells用于指定子节点reg 属性“地址字段”所占的长度单元格cells 的个数。#size-cells用于指定子节点reg 属性“大小字段”所占的长度单元格cells 的个数。
例如#address-cells2#address-cells1则reg 内的数据含义为reg address address size address address size因为每个cells 是一个32 位宽的数字例如需要表示一个64 位宽的地址时就要使用两个address 单元来表示。而假如#address-cells1#address-cells1则reg 内的数据含义为reg address size address size address size。
总之#size-cells 和#address-cells 决定了子节点的reg 属性中哪些数据是“地址”哪些数据是“长度”信息。
reg 属性
属性值类型地址、长度数据对
reg 属性描述设备资源在其父总线定义的地址空间内的地址。通常情况下用于表示一块寄存器的起始地址偏移地址和长度在特定情况下也有不同的含义。例如上例中#address-cells 1#address-cells 1reg 0x9000000 x4000其中0x9000000 表示的是地址0x4000 表示的是地址长度这里的reg 属性指定了起始地址为0x9000000长度为0x4000 的一地址空间。
ranges
属性值类型任意数量的 子地址、父地址、地址长度 编码
示例
列表8: ranges 属性
soc {#address-cells 1;#size-cells 1;compatible simple-bus;interrupt-parent gpc;ranges;busfreq {/*-------------以下内容省略--------------*/};
}该属性提供了子节点地址空间和父地址空间的映射转换方法常见格式是ranges 子地址, 父地址, 转换长度。如果父地址空间和子地址空间相同则无需转换如示例中所示只写了renges, 内容为空我们也可以直接省略renges 属性。
比如对于#address-cells 和#size-cells 都为1 的话以ranges0x0 0x10 0x20 为例表示将子地址的从0x0~(0x0 0x20) 的地址空间映射到父地址的0x10~(0x10 0x20)。
name 和device_type
属性值类型字符串。
示例
列表9: name 属性
example{name name
}列表10: device_type 属性
cpus {
#address-cells 1;
#size-cells 0;cpu0: cpu0 {compatible arm,cortex-a7;device_type cpu;reg 0;}
}这两个属性很少用已经被废弃不推荐使用。name 用于指定节点名在旧的设备树中它用于确定节点名现在我们使用的设备树已经弃用。device_type 属性也是一个很少用的属性只用在CPU 和内存的节点上。如上例中所示device_type 用在了CPU 节点。
追加/修改节点内容
列表11: 追加/修改节点内容
cpu0{//cpu-supply vddcore;clock-frequency 650000000;
};这些源码并不包含在根节点“/{⋯}”内它们不是一个新的节点而是向原有节点追加内容。以上方源码为例“cpu0”表示向“节点标签”为“cpu0”的节点追加数据这个节点可能定义在本文件也可能定义在本文件所包含的设备树文件中本例子中源码的“cpu0”定义在“stm32mp157c.dtsi”文件中。
特殊节点
aliases 子节点
aliases 子节点的作用就是为其他节点起一个别名如下所示。
列表12: 别名子节点
aliases {ethernet0 ethernet0;serial0 uart4;serial1 usart1;serial2 usart2;serial3 usart3;/*----------- 以下省略------------*/
}以“serial0 uart4;”为例。“serial0”是一个节点的名字设置别名后我们可以使用“serial0”来指代uart4 节点与节点标签类似。在设备树中更多的是为节点添加标签没有使用节点别名别名的作用是“快速找到设备树节点”。在驱动中如果要查找一个节点通常情况下我们可以使用“节点路径”一步步找到节点。也可以使用别名“一步到位”找到节点。
chosen 子节点
chosen 子节点位于根节点下如下所示
列表13: chosen 子节点
chosen {stdout-path serial0:115200n8;
};chosen 子节点不代表实际硬件它主要用于给内核传递参数。这里只设置了“stdout-path ”serial0:115200n8”;”一条属性表示系统标准输出stdout 使用串口serial0。此外这个节点还用作uboot 向linux 内核传递配置参数的“通道”我们在Uboot 中设置的参数就是通过这个节点传递到内核的这部分内容是uboot 和内核自动完成的作为初学者我们不必深究。
在中断、时钟部分也有自己的节点标准属性随着深入的学习我们会详细介绍这些节点标准属性。
如何获取设备树节点信息
在设备树中“节点”对应实际硬件中的设备我们在设备树中添加了一个“led”节点正常情况下我们可以从这个节点获取编写led 驱动所用到的所有信息例如led 相关控制寄存器地址、led时钟控制寄存器地址等等。
这一小节我们就开始学习如何从设备树的设备节点获取我们想要的数据。内核提供了一组函数用于从设备节点获取资源设备节点中定义的属性的函数这些函数以of_ 开头称为OF 操作函数。常用的OF 函数介绍如下
查找节点函数
根据节点路径寻找节点函数
列表14: of_find_node_by_path 函数(内核源码/include/linux/of.h)
struct device_node *of_find_node_by_path(const char *path)参数
path指定节点在设备树中的路径。
返回值
**device_node**结构体指针如果查找失败则返回NULL否则返回device_node 类型的结构体指针它保存着设备节点的信息。
device_node 结构体如下所示。
列表15: device_node 结构体
struct device_node {const char *name;const char *type;phandle phandle;const char *full_name;struct fwnode_handle fwnode;struct property *properties;struct property *deadprops; /* removed properties */struct device_node *parent;struct device_node *child;struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)struct kobject kobj;
#endifunsigned long _flags;void *data;
#if defined(CONFIG_SPARC)const char *path_component_name;unsigned int unique_id;struct of_irq_controller *irq_trans;
#endif
};name节点中属性为name 的值type节点中属性为device_type 的值full_name节点的名字在device_node 结构体后面放一个字符串full_name 指向它properties链表连接该节点的所有属性parent指向父节点child指向子节点sibling指向兄弟节点
得到device_node 结构体之后我们就可以使用其他of 函数获取节点的详细信息。
根据节点名字寻找节点函数
列表16: of_find_node_by_name 函数(内核源码/include/linux/of.h)
struct device_node *of_find_node_by_name(struct device_node *from,const char *name);参数 from指定从哪个节点开始查找它本身并不在查找行列中只查找它后面的节点如果设置为NULL 表示从根节点开始查找。 name要寻找的节点名。
返回值
device_node结构体指针如果查找失败则返回NULL否则返回device_node 类型的结构体指针它保存着设备节点的信息。
根据节点类型寻找节点函数
列表17: of_find_node_by_type 函数(内核源码/include/linux/of.h)
struct device_node *of_find_node_by_type(struct device_node *from,const char *type)参数 from指定从哪个节点开始查找它本身并不在查找行列中只查找它后面的节点如果设置为NULL 表示从根节点开始查找。 type要查找节点的类型这个类型就是device_node- type。
返回值
device_node device_node 类型的结构体指针保存获取得到的节点。同样如果失败返回NULL。
根据节点类型和compatible 属性寻找节点函数
列表18: of_find_compatible_node 函数(内核源码/include/linux/of.h)
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)相比of_find_node_by_name 函数增加了一个compatible 属性作为筛选条件。
参数
from指定从哪个节点开始查找它本身并不在查找行列中只查找它后面的节点如果设置为NULL 表示从根节点开始查找。type要查找节点的类型这个类型就是device_node- type。compatible要查找节点的compatible 属性。
返回值
device_node device_node 类型的结构体指针保存获取得到的节点。同样如果失败返回NULL。
根据匹配表寻找节点函数
列表19: of_find_matching_node_and_match 函数(内核源码/include/linux/of.h)
static inline struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)可以看到该结构体包含了更多的匹配参数也就是说相比前三个寻找节点函数这个函数匹配的参数更多对节点的筛选更细。参数match查找得到的结果。
参数
from指定从哪个节点开始查找它本身并不在查找行列中只查找它后面的节点如果设置为NULL 表示从根节点开始查找。matches源匹配表查找与该匹配表想匹配的设备节点。of_device_id结构体如下。
返回值
device_node device_node 类型的结构体指针保存获取得到的节点。同样如果失败返回NULL。
列表20: of_device_id 结构体
/** Struct used for matching a device*/struct of_device_id {char name[32];char type[32];char compatible[128];const void *data;
};name节点中属性为name 的值type节点中属性为device_type 的值compatible节点的名字在device_node 结构体后面放一个字符串full_name 指向它data链表连接该节点的所有属性
寻找父节点函数
列表21: of_get_parent 函数(内核源码/include/linux/of.h)
struct device_node *of_get_parent(const struct device_node *node)参数
node指定谁节点要查找父节点。
返回值
device_node device_node 类型的结构体指针保存获取得到的节点。同样如果失败返回NULL。
寻找子节点函数
列表22: of_get_next_child 函数(内核源码/include/linux/of.h)
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)参数
node指定谁节点要查找它的子节点。prev前一个子节点寻找的是prev 节点之后的节点。这是一个迭代寻找过程例如寻找第二个子节点这里就要填第一个子节点。参数为NULL 表示寻找第一个子节点。返回值device_node device_node 类型的结构体指针保存获取得到的节点。同样如果失败返回NULL。
这里介绍了7 个寻找节点函数这7 个函数有一个共同特点——返回值类型相同。只要找到了节点就会返回节点对应的device_node 结构体在驱动程序中我们就是通过这个device_node 获取设备节点的属性信息、顺藤摸瓜查找它的父、子节点等等。第一函数of_find_node_by_path 与后面六个不同它是通过节点路径寻找节点的“节点路径”是从设备树源文件(.dts) 中的到的。而中间四个函数是根据节点属性在某一个节点之后查找符合要求的设备节点这个“某一个节点”是设备节点结构体device_node也就是说这个节点是已经找到的。最后两个函数与中间四个类似只不过最后两个没有使用节点属性而是根据父、子关系查找。
提取属性值的of 函数
上一小节我们讲解了7 个查找节点的函数它们有一个共同特点找到一个设备节点就会返回这个设备节点对应的结构体指针device_node*。这个过程可以理解为把设备树中的设备节点“获取”到驱动中。“获取”成功后我们再通过一组of 函数从设备节点结构体device_node中获取我们想要的设备节点属性信息。
查找节点属性函数
列表23: of_find_property 函数(内核源码/include/linux/of.h)
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)参数
np指定要获取那个设备节点的属性信息。name属性名。lenp获取得到的属性值的大小这个指针作为输出参数这个参数“带回”的值是实际获取得到的属性大小。
返回值
property获取得到的属性。property 结构体我们把它称为节点属性结构体如下所示。失败返回NULL。从这个结构体中我们就可以得到想要的属性值了。
列表24: property 属性结构体
struct property {char *name;int length;void *value;struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)struct bin_attribute attr;
#endif
};name属性名length属性长度value属性值next下一个属性
读取整型属性函数
读取属性函数是一组函数分别为读取8、16、32、64 位数据。
列表25: of_property_read_uX_array 函数组(内核源码/include/linux/of.h)
//8 位整数读取函数
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)//16 位整数读取函数
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)//32 位整数读取函数
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)//64 位整数读取函数
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)参数
np指定要读取那个设备节点结构体也就是说读取那个设备节点的数据。propname指定要获取设备节点的哪个属性。out_values这是一个输出参数是函数的“返回值”保存读取得到的数据。sz这是一个输入参数它用于设置读取的长度。
返回值
返回值成功返回0错误返回错误状态码非零值-EINVAL属性不存在-ENODATA没有要读取的数据-EOVERFLOW属性值列表太小。
简化后的读取整型属性函数
这里的函数是对读取整型属性函数的简单封装将读取长度设置为1。用法与读取属性函数完全一致这里不再赘述。
列表26: of_property_read_uX 函数组(内核源码/include/linux/of.h)
//8 位整数读取函数
int of_property_read_u8 (const struct device_node *np, const char *propname,u8 *out_values)//16 位整数读取函数
int of_property_read_u16 (const struct device_node *np, const char *propname,u16 *out_values)//32 位整数读取函数
int of_property_read_u32 (const struct device_node *np, const char *propname,u32 *out_values)//64 位整数读取函数
int of_property_read_u64 (const struct device_node *np, const char *propname,u64 *out_values)读取字符串属性函数
在设备节点中存在很多字符串属性例如compatible、status、type 等等这些属性可以使用查找节点属性函数of_find_property 来获取但是这样比较繁琐。内核提供了一组用于读取字符串属性的函数介绍如下
列表27: of_property_read_string 函数(内核源码/include/linux/of.h)
int of_property_read_string(const struct device_node *np,const char *propname,const char **out_string)参数
np指定要获取那个设备节点的属性信息。propname属性名。out_string获取得到字符串指针这是一个“输出”参数带回一个字符串指针。也就是字符串属性值的首地址。这个地址是“属性值”在内存中的真实位置也就是说我们可以通过对地址操作获取整个字符串属性一个字符串属性可能包含多个字符串这些字符串在内存中连续存储使用’0’分隔。
返回值
返回值成功返回0失败返回错误状态码。
这个函数使用相对繁琐推荐使用下面这个函数。
列表28: of_property_read_string_index 函数(内核源码/include/linux/of.h)
int of_property_read_string_index(const struct device_node *np,const char *propname, int index,const char **out_string)相比前面的函数增加了参数index它用于指定读取属性值中第几个字符串index 从零开始计数。第一个函数只能得到属性值所在地址也就是第一个字符串的地址其他字符串需要我们手动修改移动地址非常麻烦推荐使用第二个函数。
读取布尔型属性函数
在设备节点中一些属性是BOOL 型当然内核会提供读取BOOL 型属性的函数介绍如下
列表29: of_property_read_string_index 函数(内核源码/include/linux/of.h)
static inline bool of_property_read_bool(const struct device_node *np, const char *propname)参数
np指定要获取那个设备节点的属性信息。propname属性名。
返回值
这个函数不按套路出牌它不是读取某个布尔型属性的值仅仅是读取这个属性存在或者不存在。如果想要或取值可以使用之前讲解的“全能”函数查找节点属性函数of_find_property。
内存映射相关of 函数
在设备树的设备节点中大多会包含一些内存相关的属性比如常用的reg 属性。通常情况下得到寄存器地址之后我们还要通过ioremap 函数将物理地址转化为虚拟地址。现在内核提供了of 函数自动完成物理地址到虚拟地址的转换。介绍如下
列表30: of_iomap 函数(内核源码/drivers/of/address.c)
void \__iomem *of_iomap(struct device_node *np, int index)参数
np指定要获取那个设备节点的属性信息。index通常情况下reg 属性包含多段index 用于指定映射那一段标号从0 开始。
返回值
成功得到转换得到的地址。失败返回NULL。
内核也提供了常规获取地址的of 函数这些函数得到的值就是我们在设备树中设置的地址值。介绍如下
列表31: of_address_to_resource 函数(内核源码/drivers/of/address.c
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)参数
np指定要获取那个设备节点的属性信息。index通常情况下reg 属性包含多段index 用于指定映射那一段标号从0 开始。r这是一个resource 结构体是“输出参数”用于返回得到的地址信息。
返回值
成功返回0失败返回错误状态码。
resource 结构体如下所示
struct resource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;unsigned long desc;struct resource *parent, *sibling, *child;
};start起始地址end结束地址name属性名字
从这个结构体比较简单很容从中得到获取得到的具体信息。这里不再赘述。
这里介绍了三类常用的of 函数这些基本满足我们的需求其他of 函数后续如果使用到我们在详细介绍。
向设备树中添加设备节点实验
实验说明
通常情况下我们几乎不会从零开始写一个设备树因为一个功能完善的设备树通常比较庞大例如本教程引用的ST 官方编写的设备树“stm32mp157c.dtsi”就多达2000 行另外官方已经写好了主干的部分我们只需要引用官方写好的设备树然后根据自己的实际情况修改即可。
本节实验使用野火STM32MP157 S1 Pro 开发板开发板上的系统保持不变。
实验准备
在板卡上的部分GPIO 可能会被系统占用在使用前请根据需要修改/boot/uEnv.txt 文件可注释掉某些设备树插件的加载重启系统释放相应的GPIO 引脚。
如本节实验中可能在鲁班猫系统中默认使能了LED 的设备功能用在了LED 子系统。引脚被占用后设备树可能无法再加载或驱动中无法再申请对应的资源。
方法参考如下 取消LED 设备树插件以释放系统对应LED 资源操作如下 如若运行代码时出现“Device or resource busy”或者运行代码卡死等等现象请按上述情况检查并按上述步骤操作。
如出现Permission denied 或类似字样请注意用户权限大部分操作硬件外设的功能几乎都需要root 用户权限简单的解决方案是在执行语句前加入sudo 或以root 用户运行程序。
代码讲解
本章的示例代码目录为linux_driver/device_tree
在实际应用中我们最常见的操作是向设备节点中增加一个节点、向现有设备节点追加数据、和编写设备树插件。
根据之前讲解 我们的系统默认使用的是“ebf_linux_kernel/arch/arm/boot/dts/stm32mp157a-basic.dts”设备树我们就在这个设备树里尝试增加一个设备节点如下所示。
列表33: 添加子节点
/ {model Embedfire STM32MP157 Star LubanCat Robot S1 Board;compatible st,stm32mp157a-dk1, st,stm32mp157;aliases {thernet0 ethernet0;serial0 uart4;serial1 usart1;serial2 usart2;serial3 usart3;};/* 添加led 节点*/led_test{#address-cells 1;#size-cells 1;rgb_led_red0x50002000{compatible fire,rgb_led_red;reg 0x50002000 0x00000020;status okay;};};
};在我们在stm32mp157a-basic.dts 设备树文件的根节点末尾新增了一个节点名为“led_test”的节点里面只添加了几个基本属性我们这里只是学习添加一个设备节点。
在以上代码中led_test 节点的#address-cells 1#size-cells 1意味着它的子节点的reg 属性里的数据是“地址”、“长度”交替的。
第二部分是led 节点的子节点它定义了三个属性分别为compatible、reg、status这三个属性在“节点属性”章节已经介绍。需要注意的是rgb 属性在父节点设置了#address-cells 1#sizecells 1所以这里0x50002000 表示的是地址这里填写的是GPIO 控制寄存器的首地址0x00000020 表示的是地址长度。“rgb_led_red0x50002000”中的单元的地址0x50002000 要和reg属性的第一个地址一致。
内核编译设备树
编译内核时会自动编译设备树但是编译内核很耗时所以我们推荐使用如下命令只编译设备树。
命令
make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- stm32mp157_ebf_defconfig
make ARCHarm -j4 CROSS_COMPILEarm-linux-gnueabihf- dtbs编译成功后生成的设备树文件.dtb 位于源码目录下的/arch/arm/boot/dts/ 文件名为“stm32mp157a-basic.dtb”
程序结果
下载设备树
同SCP 或NFS 将编译的设备树拷贝到开发板上。替换/boot/dtbs/stm32mp157a-basic.dtb。
uboot 在启动的时候负责该目录的设备文件加载到内存供内核解析使用。
重启开发板。
实验结果
设备树中的设备树节点在文件系统中有与之对应的文件位于“/proc/device-tree”目录。进入“/proc/device-tree”目录如下所示。 接着进入led 文件夹可以发现led 节点中定义的属性以及它的子节点如下所示。 在节点属性中多了一个name我们在led 节点中并没有定义name 属性这是自从生成的保存节点名。
这里的属性是一个文件而子节点是一个文件夹我们再次进入“rgb_led_red0x50002000”文件夹。里面有compatible、name、reg、status 四个属性文件。我们可以使用“cat”命令查看这些属性文件如下所示。 至此我们已经成功的在设备树中添加了一个名为“led_test”的节点。
在驱动中获取节点属性实验
本实验目的是演示如何使用上一小节讲解的of 函数进行本实验之前要先完成“在设备树中添加设备节点实验”因为本实验就是从我们添加的节点中获取设备节点属性。
实验说明
本实验是一个简化的字符设备驱动在驱动中没有实际操作硬件仅在open 函数中调用of 函数获取设备树节点中的属性获取成功后打印获取得到的内容。
代码讲解
列表34: 获取节点属性实验
/*.open 函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{int error_status -1;printk(\n open form device \n);/* 获取DTS 属性信息*/led_device_node of_find_node_by_path(/led_test);if(led_device_node NULL){printk(KERN_ALERT \n get led_device_node failed ! \n);return -1;}/* 根据led_device_node 设备节点结构体输出节点的基本信息*/printk(KERN_ALERT name: %s,led_device_node-name); //输出节点名printk(KERN_ALERT child name: %s,led_device_node-child-name); //输出子节点的节点名/* 获取rgb_led_red_device_node 的子节点*/rgb_led_red_device_node of_get_next_child(led_device_node,NULL);if(rgb_led_red_device_node NULL){printk(KERN_ALERT \n get rgb_led_red_device_node failed ! \n);return -1;}printk(KERN_ALERT name: %s,rgb_led_red_device_node-name); //输出节点名printk(KERN_ALERT parent name: %s,rgb_led_red_device_node-parent-name); //输出父节点的节点名/* 获取rgb_led_red_device_node 节点的compatible 属性*/rgb_led_red_property of_find_property(rgb_led_red_device_node,compatible,size);if(rgb_led_red_property NULL){printk(KERN_ALERT \n get rgb_led_red_property failed ! \n);return -1;}printk(KERN_ALERT size : %d,size); //实际读取得到的长度printk(KERN_ALERT name: %s,rgb_led_red_property-name); //输出属性名printk(KERN_ALERT length: %d,rgb_led_red_property-length); //输出属性长度printk(KERN_ALERT value : %s,(char*)rgb_led_red_property-value); //属性值/* 获取reg 地址属性*/error_status of_property_read_u32_array(rgb_led_red_device_node,reg,out_values, 2);if(error_status ! 0){printk(KERN_ALERT \n get out_values failed ! \n);return -1;}printk(KERN_ALERT0x%08X , out_values[0]);printk(KERN_ALERT0x%08X , out_values[1]);return 0;
}第9-14 行使用“of_find_node_by_path”函数寻找“test_led”设备节点。参数是“led_test”的设备节点路径。第16-17 行获取成功后得到的是一个device_node 类型的结构体指针然后我们就可以从这个结构体中获得我们想要的数据。获取完整的属性信息可能还需要使用其他of 函数。第20-27 行获取rgb_led_red_device_node 的子节点在第二部分我们得到了“led”节点的“设备节点结构体”这里就可以使用“of_get_next_child”函数获取它的子节点。当然我们也可以从“led”节点的“设备节点结构体”中直接读取得到它的第一个子节点。第30-39 行使用“of_find_property”函数获取“rgb_led_red”节点的“compatible”属性。第42-49 行使用“of_property_read_u32_array”函数获取reg 属性。
进入到驱动模块文件夹中编译驱动模块
make该文件夹会产生get_dts_info.ko 驱动模块
程序结果
编译成功后将驱动.ko 拷贝到开发板使用insmod 安装驱动模块然后可以在/dev/目录下找到get_dts_info。 向驱动模块随便输入一个字符
sudo sh -c echo 1 /dev/get_dts_info从上图中可以看到驱动程序中得到了设备树中设置的属性值。 参考资料嵌入式Linux 驱动开发实战指南-基于STM32MP1 系列