Linux驱动开发之设备树

2020-02-21

关键字:dts解析、dts语法


什么是设备树?

设备树:device tree。它是Linux开发中用于描述硬件信息的文件。如:数量、类别、地址、总线情况与中断等。设备树文件其实就是一种适合人类阅读的文本文件,它以 .dts 作为后缀,通常保存在 ./arch/arm/boot/dts 目录下。dts 文件也是可以编译的,它的编译产物是 .dtb 文件,这个文件会在 bootloader 中被读取,并传递给 kernel。

在早期的 Linux 版本中,如 Linux 2.6 及以前,设备的信息是直接依附于程序代码的,它们被保存在 arch/arm/plat-xxx 与 arch/arm/mach-xxx 两个目录内。

dts的编译:

.dts 文件可以经 dtc 编译器编译生成 .dtb 文件,.dtb 文件就可以直接下载进开发板中运行使用了。dtc 编译器的源码位于内核目录的 scripts/dtc 目录内。.dtb 文件一般是在 bootcmd 中被指定的,由此 bootloader 就会去加载设备树信息了。

设备树的语法

以下是一个dts的语法模板:

/{
    node1{
        a-string-property="A string";
        a-string-list-property="first string","second string";
        a-byte-data-property=[0x01 0x23 0x34 0x56];
        child-node1{
            first-child-property;
            second-child-property=<1>;
            a-string-property="hello world";
        };
        child-node2{
        
        };
    };
    node2{
        an-empty-property;
        a-cell-perperty=<1 2 3 4>;/*each number(cell) is a uint32*/
        child-node1{
        
        };
    };
};

设备树中的信息都是以“节点”存在的。首先整个设备树有且只有一个“根节点”,它以 /{}; 表示。所有的设备信息描述都是在根节点下面写明的。节点可以嵌套。设备树描述信息以键值对形式实现。除了典型的 key-value 对形式外,也可以写仅有 key 没有 value 的属性,它被称为“标识位”,如上面模板中的 first-child-property; 所示。

关于节点名称:

除根节点以外,每一个节点都必须要有一个名字,其形式为:<名字>[@<设备地址>]。尖括号内的表示必填项,方括号内的表示选填项,若有方括号中的内容时必须要添加一个 @ 字符用于分隔。例如:

/{
    mipi_phy: video-phy@10020710 {
        //...
    };
    
    camera {
        //...
    };
        
    keypad@100A0000 {
        //...
    };
};

节点名称中,第一部分的“名字”可以随意填写,但最好起一个能够快速辨别的名字。第一部分的名字应不超过31个ASCII字符。第二部分的设备地址就比较直白了,不过其实这里也并不一定非得填设备地址,其实也是可以自由填写的,真正的设备地址其实是要在节点内部以reg属性标明的。同级节点的完整名称必须唯一。

关于属性property:

设备树语法中的属性是以键值对形式表现的。它的值可以为空也可以包含任意字节流。属性的几个基本数据表示形式如下:

1、文本字符串

其值用双引号表示,如:string-property="a string"

2、Cells

32位的无符号整数,用尖括号限定,如:cell-property=<0xbeef 123 0xafbd>

3、二进制数据

用方括号限定,如:binary-property=[01 23 33 42]

4、自由集合

不同形式的灵气可以通过逗号连在一起,如:mixed-property="a string",[01 09 76],<0x12 0x44 0x87>;

5、字符串列表

通过逗号可以创建字符串列表,如:string-list="red fish", "gold fish";

compatible 属性:

这个属性是一个常见属性,基本上每一个节点都会有它的踪影。这个属性中的值常用于在代码中作匹配使用。它的值的命名形式通常以 "<制造商>,<型号>" 为格式。compatible 的命名最好不要使用通配字符,而应该是要确切地写出它的名称。例如,我们就应该写成 "compatible=<rockchip,3128>" 而不应写成 "compatible=<rockchip,31xx>" 。

#address-cells 与 #size-cells 属性:

这两条属性是用于限定其子节点(仅下一级节点)中reg属性的。直接看下面的例子:

external-bus{

  #address-cells=<2>;

  #size-cells=<1>;

  ethernet@0,0{

    compatible="smc,smc91c11";

    reg=<0 0 0x1000>;

    interrupts=<5 2>;

  };

};

#address-cells为2,表示 reg 中的地址的长度占 2 个数。在上面的例子中就是 reg 中的 0 0 表示地址。

#size-cells为1,表示 reg 中的长度的值占 1 个数。在上面的例子中就是 0x1000。

有了这两条限定,external-bus 节点内的所有子节点的 reg 都必须要填 3 个值,其中前面两个值表示地址,第三个值表示长度。

需要注意的是,这两条属性仅限定它所在的节点中的子节点。这两条属性所在节点的子节点的子节点是不受它的管控的。

reg 属性:

reg 属性是用于描述地址值的。它的形式通常为: reg = <address1 length1 [address2 length2] [address3 length3]>; 

中断信息属性:

描述中断需要四个属性共同作用:

1、interrupt-controller

这条属性是没有值的。它用于将该节点定义为一个接收中断信号的设备,即中断控制器。

2、#interrupt-cells

这条属性用于声明该中断控制器的中断指示符中cell的个数。类似于 #address-cells 和 #size-cells。

3、interrupt-parent

4、interrupts

用于描述中断指示符的列表,对应于该设备上的每个中断输出信号。

以下是一个中断语法的示例:

serial 节点描述了一个中断信息,intc 节点则是一个中断控制器。interrupt-parent 中的中断信息将会被 serial 使用到,serial 中的 interrupts 所使用的值就是 &intc 中的。而因为 intc 是中断控制器,所以当 serial 中断产生时,将会先到达 intc 再到 CPU。所以 #interrupt-cells 中的值所限定的就是 serial 中的 reg 。

编写代码去读取设备树中的信息

Linux内核中提供了一些专门用于读取设备树信息的函数接口,它们的签名如下:

//跟据节点路径查找device_node结构对象
struct device_node *of_find_node_by_path(const char *path);

//根据property中的name来在device_node中查找property.
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);

//比较compat参数与指定节点中的compatible中的值,类似于strcmp函数
int of_device_is_compatible(const struct device_node *device, const char *compat);

//获得父节点
struct device_node *of_get_parent(const struct device_node *node);

//根据属性名称读出指定数量个属性数组。
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz);

//读取该节点的第index个中断号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);

下面通过一个小程序来演示读取dts中记载的信息:

假设我们有如下dts描述信息:

test_node@12345{
    compatible = "farsigt,test";
    reg = <0x123344 0x28
            0x76543 0xfe>;
    testprop,mytest;
    test_list_string = "red fish","fly fish","blue fish";
    interrupt-parent = <&gpx1>;
    interrupts=<1 2>;
};

这段描述信息只要写进内核设备树中并正确编译,就可以在 /proc/device-tree 目录下找到一个名称为 test_node@12345 的目录,其内包含有这个节点所描述的所有信息。

则可以有如下示例驱动程序:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>

irqreturn_t key_irq_handler(int irqno, void *devid)
{
    printk("key_irq_handler()
");
    
    return IRQ_HANDLED;
}

static int __init dts_drv_init()
{
    //要在代码中获取到所有的节点信息,首先要把这个节点获取到。
    struct device_node *np = of_find_node_by_path("/test_node@12345");
    printk("name:%s, full name:%s
", np->name, np->full_name);
    
    
    //获取到节点中的属性信息。
    struct property *prop = of_find_property(np, "compatible", NULL);
    printk("compatible prop name:%s, prop value:%s, prop length:%d
", prop->name, prop->value, prop->length);
    
    
    //一个专门针对 compatible 属性的函数接口。
    int ret = of_device_is_compatible(np, "farsight,test");//np节点中是否有一个名字叫 farsight,test的compatible属性?
    printk("Is have 'farsight,test' compatible? %d
", ret);
    
    
    //获取比较特殊的属性值。
    u32 regdata[4];
    ret = of_property_read_u32_array(np, "reg", regdata, 4);//这个size要根据dts中实际的数量来。
    if(!ret)
    {
        printk("get ret array success.0x%x,0x%x,0x%x,0x%x
", regdata[0], regdata[1], regdata[2], regdata[3]);
    }
    
    
    //获取字符列表。
    const char *pstr[3];//有几个数值就创建几个大小的指针数组。
    ret = of_property_read_string(np, "test_list_string", pstr/*这个方式是否正确?*/);
    if(!ret)
    {
        printk("string list,%s,%s,%s
", pstr[0], pstr[1], pstr[2]);
    }
    //或还有一种方式。
    int i = 0;
    for(; i < 3; i++)
    {
        ret = of_property_read_string_index(np, "test_list_string", i, &pstr[i]);
        if(!ret)
        {
            printk("%s
", pstr[i]);
        }
    }
    
    
    //获取标志属性。
    if(of_find_property(np, "testprop,mytest", NULL))
    {
        printk("You have this property,does it mean any thing?
");
    }
    
    
    //获取到中断号码。
    int irqno = irq_of_parse_and_map(np, 0/*从0开始获取。*/);
    printk("irqno:%d
", irqno);
    //中断号获取到了就去申请中断以验证是否有效。
    ret = request_irq(irqno, key_irq_handler, IRQ_TRIGGER_FALLING|IRQ_TRIGGER_RISING, "key_irqqq", NULL);
    
    return 0;
}

static int __exit dts_drv_exit()
{
    free_irq(irqno, NULL);//与申请中断时的参数一致。
}

module_init(dts_drv_init);
module_exit(dts_drv_exit);
MODULE_LICENSE("GPL");

原文地址:https://www.cnblogs.com/chorm590/p/12333397.html