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()\n");
    
    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\n", 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\n", 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\n", 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\n", 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\n", 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\n", pstr[i]);
        }
    }
    
    
    //獲取標志屬性。
    if(of_find_property(np, "testprop,mytest", NULL))
    {
        printk("You have this property,does it mean any thing?\n");
    }
    
    
    //獲取到中斷號碼。
    int irqno = irq_of_parse_and_map(np, 0/*從0開始獲取。*/);
    printk("irqno:%d\n", 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");

 


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM