2021-07-18

设备树基础知识

  • 设备树文件类型

.dts 文件表示是设备树的源文件,里面可以添加各种设备的相关节点信息。

.dtsi 文件表示是设备输的头文件,其作用类似c语言中的头文件,需要用到时,也是#inxlude <xxx.dtsi>,其类容一般是属性相似的设备的节点信息。

.dtb 文件表示被编译成二进制的设备树文件。

  • 设备树的编译工具

dtc 就是设备树的编译工具。

基本用法如下:

​ dtc -S .dts -O .dtb -o xxx.dtb xxx.dts

上条指令中 dtc表示采用dtc工具进行编译, 参数 -S 表示被编译的文件类型,-O 表示编译后输出的文件类型,-o表示指定生成的编译文件名字,xxx.dtb就是我们指定的生成文件名字,xxx.dts是被编译的文件名字。

  • 设备树的基本框架

/dts-v1/ /一个完整的设备树文件必须加这一行,不然编译会报错/

/ { /根节点,一个完整的设备树必须且只有一个根节点。虽然我们常看见每个设备树文件都有一个根节点,但是在编译时会整合成一个/

​ node_label : nodename{ /node_label表示设备节点的便签类似于别名,方便其他地方引用。nodename表示设备节点名字。/

​ string-porperty = “a string”; /字符串属性的格式/

​ string-list = “a string”, “a string”; /多个字符串属性的格式/

​ one-int-porperty = <126>; /一个单个属性的格式/

​ int-list-property = <0xbeef 123 0xadfs>; /多个单元格属性的格式,每个单元格是32位,这里定义了三个单元格的属性/

​ byte-array-property = [0x12 0x22]; /多个8位属性的格式/

​ boolean-porperty; /无值的属性,也可以理解成Bool类型,存在表示true,反之成立/

​ };

};

以下是设备树使用的一些数据类型的定义及语法:

  • 文本字符串用双引号,多个值可以用逗号隔开。

  • 单元格是由尖括号包含的32位整数,多个值可以用空格隔开。

  • 布尔类型的值为空。

  • 属性的名字除了有很多常用的,也可以自定义

  • 每条属性是以“;”符号结尾的,类似c语言语句。

  • 设备树文件中注释类似c语言中多行注释一样。

  • 设备树文件中有些属性开头有“#”,这并不代表注释,而是属性名字的一部分而已,并且通常以“#”开头的属性都表示其属性描述的是某一个属性的长度或者size。

  • 简而言之,设备树就是由一个一个的设备节点构成,而每个节点又是由一些属性构成,这些属性就是具体设备的信息。

设备树中特殊的节点

  • chosen节点

​ chosen节点是一个特殊的节点,它并不用来描述特定的设备信息,而是用于传递一些数据给OS,比如bootloader的启动参数。

chosen{

​ bootarge = “root =/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200”;

};

  • aliase节点

​ 该节点也不是用来描述具体设备信息的,而是集合了一些节点别名的定义。
在这里插入图片描述
注:该图片来源于https://blog.csdn.net/weixin_45309916/article/details/109880928 ,如有侵权,请联系653382021@qq.com删除。

设备节点常见属性

  • compatible属性
    一般的,用于匹配设备节点和设备驱动,规则是驱动设备ID表中的compatible域的值(字符串),和设备树中设备节点中的compatible属性值完全一致,则节点的内容是给驱动的。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:相关变量的值或名称可能不匹配,这里主要是想表达一个驱动和设备树对应的关系。

  • address属性

#address-cells:描述子节点reg属性值的地址表中首地址cell数量
#size-cells:描述子节点reg属性值的地址表中地址长度cell数量
reg:描述地址表
在这里插入图片描述

  • CPU的地址描述

每个CPU都分配了唯一的一个ID,描述没有大小的CPU ids.

在这里插入图片描述

  • 内存映射设备

描述一个设备的内存地址的时候,一般使用1个cell(32bits)描述地址,紧接着1一个cell (32bits)描述地址长度。

在这里插入图片描述

  • 非内存映射设备

譬如i2c设备,有一个寻址地址,没有内存地址那样的地址长度和范围,一般使用1个cell (32bits)描述该地址,而没有描述地址长度的cell。
在这里插入图片描述

  • 地址转换范围

有些设备是有片选的,就需要描述片选及片选的偏移量,在说明地址时,还需要说明地 址映射范围。

在这里插入图片描述

  • interrupt属性

interrupt-controller 一个空属性用来声明这个node接收中断信号;

#interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单 位做中断描述符;

interrupt-parent 标识此设备节点属于哪一个中断控制器,如果没有设置这个 属性,会自动依附父节点的;

interrupts 一个中断标识符列表,表示每一个中断输出信号

在这里插入图片描述

一般的,如果父节点的#interrupt-cells的值是3,则子节点的interrupts一个cell三个32bits整型值:<中断域 中断 触发方式>
实际解析情况,得根据实际使用内核的设备树参加资料来决定。

在这里插入图片描述

一般的,如果父节点的#interrupt-cells的值是2,则子节点的interrupts一个cell两个32bits整型值:中断和触发方式。
实际解析情况,得根据实际使用内核的设备树参加资料来决定。

在这里插入图片描述

  • gpio属性

常用的属性如下:

gpio-controller:说明该节点描述的是一个gpio控制器

#gpio-cells:描述gpio使用节点的属性一个cell的内容

属性名=<&引用GPIO节点别名 GPIO标号 工作模式>;

在这里插入图片描述

  • regulator属性

常用属性如下:

regulator-name:电源管理的名字,对应struct regulation_constraints中的name

regulator-min-microvolt:设备最小电压对应struct regulation_constraints中的min_uV

regulator-max-microvolt:设备最大电压

regulator-always-on:对应struct regulation_constraints中的always_on

regulator-boot-on:对应struct regulation_constraints中的boot_on

在这里插入图片描述

关于regulator的详细介绍可参考:https://cloud.tencent.com/developer/article/1603958

  • pinctrl属性

常用属性:

pinctrl-names:常见的值为default\sleep,对应了列表项的名称。

pinctrl-:提供设备某种状态需要的Pinctrl配置列表。

在这里插入图片描述

驱动中提取设备信息的常用函数

  • 每一个节点都转换为一个device_node结构体

在这里插入图片描述

  • device_node结构体中有properties, 用来表示该节点的属性, 每一个属性对应一个property结构体:

在这里插入图片描述
查找节点的OF函数

  1. of_find_node_by_name函数
    of_find_node_by_name函数通过节点名字查找指定的节点,函数原型如下:
    struct device_node*of_find_node_by)name(struct device_node *from,const char *name);
    from : 开始查找的节点,如果为NULL表示从根节点开始查找整个设备树
    name : 要查找的节点名字
    返回值:找到的节点,如果为NULL表示查找失败

  2. of_find_node_by_type 函数
    of_find_node_by_type函数通过 device_type 属性查找指定的节点,函数原型如下:
    struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
    from: 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
    type: 要查找的节点对应的 type 字符串,也就是 device_type 属性值。
    返回值: 找到的节点,如果为 NULL 表示查找失败。

  3. of_find_compatible_node 函数
    of_find_compatible_node 函数根据 device_type 和 compatible 这两个属性查找指定的节点,函数原型如下:
    struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)
    from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
    type:要查找的节点对应的 type 字符串,也就是 device_type 属性值,可以为 NULL,表示
    忽略掉 device_type 属性。
    compatible: 要查找的节点所对应的 compatible 属性列表。
    返回值: 找到的节点,如果为 NULL 表示查找失败

  4. of_find_node_by_path 函数
    of_find_node_by_path 函数通过路径来查找指定的节点,函数原型如下:
    inline struct device_node *of_find_node_by_path(const char *path)
    函数参数和返回值含义如下:
    path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径。
    返回值: 找到的节点,如果为 NULL 表示查找失败

举个栗子

/*获取设备树中的属性数据*/
//1.获取设备节点:alphaled
dtsled.nd = of_find_node_by_path("/alphaled");
if(dtsled.nd == NULL)
{
    printk("alphaled node can not found!\r\n");
    return -EINVAL;
}
else
{
    printk("alphaled node has been found!\r\n");
}

/dtsled设备结构体/
struct dtsled_dev{
dev_t devid; /设备号/
struct cdev cdev; /cdev/
struct class *class; //
struct device device; /设备/
int major; /
主设备号 /
int minor; /
次设备号 */
struct device_node nd; / 设备节点 */
};

struct dtsled_dev dtsled; /* led 设备 */

这里的dtsled.nd的信息如下,指的是led设备的设备节点,后面所用的也是这个

/dtsled设备结构体/
struct dtsled_dev{
dev_t devid; /设备号/
struct cdev cdev; /cdev/
struct class *class; //
struct device device; /设备/
int major; /
主设备号 /
int minor; /
次设备号 */
struct device_node nd; / 设备节点 */
};

struct dtsled_dev dtsled; /* led 设备 */
查找父\子节点的OF函数
Linux内核提供了几个查找节点对应的父节点或子节点的OF函数

  1. of_get_parent 函数
    of_get_parent 函数用于获取指定节点的父节点(如果有父节点的话),函数原型如下:
    struct device_node *of_get_parent(const struct device_node *node)函数参数和返回值含义如下
    node:要查找的父节点的节点。
    返回值: 找到的父节点。

  2. of_get_next_child 函数
    of_get_next_child函数用迭代的查找子节点,函数原型如下:
    struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
    函数参数和返回值含义如下:
    node:父节点。
    prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
    返回值: 找到的下一个子节点。

提取属性值的OF函数
节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要,Linux内核中使用结构体property表示属性,此结构体同样定义在文件include/linux/of.h中,内容如下:

struct property {
char *name; //属性名字
int length; //属性长度
void *value; //属性值
struct property *next; //下一个属性值
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};

  1. of_find_property 函数
    of_find_property 函数用于查找指定的属性,函数原型如下:
    property *of_find_property(const struct device_node *np, const char *name, int *lenp)
    函数参数和返回值含义如下:
    np: 设备节点。
    name: 属性名字。
    lenp:属性值的字节数
    返回值: 找到的属性

举个栗子

struct property proper;
//2.获取compatible属性内容
proper = of_find_property(dtsled.nd,“compatible”,NULL);
if(proper == NULL)
{
printk(“compatible property find failed\r\n”);
}
else
{
printk(“compatible = %s\r\n”, (char
)proper->value);
}

  1. of_property_count_elems_of_size 函数
    of_property_count_elems_of_size 函数用于获取属性中元素的数量,比如 reg 属性值是一个数组,那么使用此函数可以获取到这个数组的大小,此函数原型如下:
    int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
    函数参数和返回值含义如下:
    np:设备节点。
    proname: 需要统计元素数量的属性名字。
    elem_size:元素长度。
    返回值: 得到的属性元素数量。

  2. of_property_read_u32_index 函数
    of_property_read_u32_index函数用于从属性中获取指定标号的 u32 类型数据值(无符号 32位),比如某个属性有多个 u32 类型的值,那么就可以使用此函数来获取指定标号的数据值,此函数原型如下:
    int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index,u32 *out_value)
    函数参数和返回值含义如下:
    **np:**设备节点。
    proname: 要读取的属性名字。
    index:要读取的值标号。
    out_value:读取到的值
    返回值: 0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据,
    -EOVERFLOW 表示属性值列表太小

  3. of_property_read_u8_array 函数
    of_property_read_u16_array 函数
    of_property_read_u32_array 函数
    of_property_read_u64_array 函数
    这 4 个函数分别是读取属性中 u8、 u16、 u32 和 u64 类型的数组数据,比如大多数的 reg 属性都是数组数据,可以使用这 4 个函数一次读取出 reg 属性中的所有数据。这四个函数的原型如下:
    int of_property_read_u8_array(const struct device_node *np,const char *propname,u8 *out_values,size_t sz)
    int of_property_read_u8_array(const struct device_node *np,const char *propname,u16 *out_values,size_t sz)
    int of_property_read_u8_array(const struct device_node *np,const char *propname,u32 *out_values,size_t sz)
    int of_property_read_u8_array(const struct device_node *np,const char *propname,u64 *out_values,size_t sz)
    函数参数和返回值含义如下:
    np:设备节点。
    proname: 要读取的属性名字。
    out_value:读取到的数组值,分别为 u8、 u16、 u32 和 u64。
    sz: 要读取的数组元素数量。
    返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据,
    -EOVERFLOW 表示属性值列表太小
    举个栗子

    /* 4、获取 reg 属性内容 */
    int ret;
    ret = of_property_read_u32_array(dtsled.nd, “reg”, regdata, 10);
    if(ret < 0)
    {
    printk(“reg property read failed!\r\n”);
    }
    else
    {
    u8 i = 0;
    printk(“reg data:\r\n”);
    for(i = 0; i < 10; i++)
    printk("%#X “, regdata[i]);
    printk(”\r\n");
    }
    5、 of_property_read_u8 函数
    of_property_read_u16 函数
    of_property_read_u32 函数
    of_property_read_u64 函数
    有些属性只有一个整形值,这四个函数就是用于读取这种只有一个整形值的属性,分别用
    于读取 u8、 u16、 u32 和 u64 类型属性值,函数原型如下:
    int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)
    int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)
    int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)
    int of_property_read_u64(const struct device_node *np,const char *propname,u64 *out_value)
    函数参数和返回值含义如下:
    np: 设备节点。
    proname: 要读取的属性名字。
    out_value:读取到的数组值。
    返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据,
    -EOVERFLOW 表示属性值列表太小。

6、 of_property_read_string 函数
of_property_read_string 函数用于读取属性中字符串值,函数原型如下:
int of_property_read_string(struct device_node *np,const char *propname,const char **out_string)
函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
out_string:读取到的字符串值。
返回值: 0,读取成功,负值,读取失败。

这也是个栗子

/* 3、获取 status 属性内容 */
int ret;
ret = of_property_read_string(dtsled.nd, "status", &str);
if(ret < 0)
{
    printk("status read failed!\r\n");
}
else 
{
    printk("status = %s\r\n",str);
}
7、 of_n_addr_cells 函数
of_n_addr_cells 函数用于获取#address-cells 属性值,函数原型如下:
int of_n_addr_cells(struct device_node *np)
函数参数和返回值含义如下:
np: 设备节点。
返回值: 获取到的#address-cells 属性值。

8、 of_n_size_cells 函数
of_size_cells 函数用于获取#size-cells 属性值,函数原型如下:
int of_n_size_cells(struct device_node *np)
函数参数和返回值含义如下:
np: 设备节点。
返回值:获取到的#size-cells 属性值。

其他常用的OF函数
of_iomap 函数
of_iomap 函数用于直接内存映射,以前我们会通过 ioremap 函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址,不需要使用 ioremap 函数了。当然了,你也可以使用 ioremap 函数来完成物理地址到虚拟地址的内存映射,只是在采用设备树以后,大部分的驱动都使用 of_iomap 函数了。 of_iomap 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是哪一段, of_iomap 函数原型如下:
np:设备节点。
index: reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
返回值: 经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。
完整的一个例子:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后,推荐一些关于设备树的文章:

https://blog.csdn.net/zj82448191/article/details/109195364

推荐理由:介绍了设备树的解析过程,还有驱动与设备树的匹配方法。

https://www.cnblogs.com/hellokitty2/p/9975711.html

https://cloud.tencent.com/developer/article/1603958

推荐理由:有完整的驱动示列,同时侧重讲的regulator管理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值