zoukankan      html  css  js  c++  java
  • 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");

  • 相关阅读:
    poj4474 Scout YYF I(概率dp+矩阵快速幂)
    网络编程之TCP异步群聊:服务器端代码
    平衡树(AVL)详解
    网络编程之TCP异步群聊:客户端代码
    [置顶] android 图片库的封装
    oracle的nvl函数的使用解析
    七天美音英标学习总结
    软考(7)——看图心想 标准化和知识产权
    Node.js学习(7)----包
    Ubuntu bitnami gitlab 安装
  • 原文地址:https://www.cnblogs.com/chorm590/p/12333397.html
Copyright © 2011-2022 走看看