• 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");

  • 相关阅读:
    GitLab 介绍
    git 标签
    git 分支
    git 仓库 撤销提交 git reset and 查看本地历史操作 git reflog
    git 仓库 回退功能 git checkout
    python 并发编程 多进程 练习题
    git 命令 查看历史提交 git log
    git 命令 git diff 查看 Git 区域文件的具体改动
    POJ 2608
    POJ 2610
  • 原文地址:https://www.cnblogs.com/chorm590/p/12333397.html
Copyright © 2020-2023  润新知