DTS

1、DTS的加載過程node

 

若是要使用Device Tree,首先用戶要了解本身的硬件配置和系統運行參數,並把這些信息組織成Device Tree source file。經過DTC(Device Tree Compiler),能夠將這些適合人類閱讀的Device Tree source file變成適合機器處理的Device Tree binary file(device tree blob)。ios

 

在系統啓動時,boot program(例如:firmware、bootloader)能夠將保存在flash中的DTB copy到內存(固然也能夠經過其餘方式,例如經過bootloader的交互式命令加載DTB,或者firmware能夠探測到device的信息,組織成DTB保存在內存中),並把DTB的起始地址傳遞給client program(例如OS kernel,bootloader或者其餘特殊功能的程序)。chrome

 

對於計算機系統(computer system),通常是firmware->bootloader->OS,對於嵌入式系統,通常是bootloader->OS。express

 

 

 

2、DTS的描述信息

Device Tree由一系列被命名的結點(node)和屬性(property)組成,而結點自己可包含子結點。

所謂屬性,其實就是成對出現的name和value。

在Device Tree中,可描述的信息包括(原先這些信息大多被hard code寫到kernel中):

  • CPU的數量和類別

  • 內存基地址和大小

  • 總線和橋

  • 外設鏈接

  • 中斷控制器和中斷使用狀況

  • GPIO控制器和GPIO使用狀況

  • Clock控制器和Clock使用狀況

它基本上就是畫一棵電路板上CPU、總線、設備組成的樹,Bootloader會將這棵樹傳遞給內核,而後內核能夠識別這棵樹,並根據它展開出Linux內核中的platform_device、i2c_client、spi_device等設備,而這些設備用到的內存、IRQ等資源,也被傳遞給了內核,內核會將這些資源綁定給展開的相應的設備。數組

 

Device Tree是否要描述系統中的全部硬件信息?答案是否認的。基本上,不須要描述那些能夠動態探測到的設備,例如USB device。不過對於SOC上的usb hostcontroller,它沒法被動態識別,須要在device tree中描述。app

 

同理,在computersystem中,PCI device能夠被動態探測到,不須要在device tree中描述,可是PCI bridge若是不能被探測,那麼就須要描述它。函數

 

.dts文件是一種ASCII 文本格式的Device Tree描述,此文本格式很是人性化,適合人類的閱讀習慣。工具

 

基本上,在ARM Linux中,一個.dts文件對應一個ARM的machine,通常放置在內核的arch/arm/boot/dts/目錄。ui

 

因爲一個SoC可能對應多個machine(一個SoC能夠對應多個產品和電路板),勢必這些.dts文件需包含許多共同的部分,Linux內核爲了簡化,把SoC公用的部分或者多個machine共同的部分通常提煉爲.dtsi,相似於C語言的頭文件。其餘的machine對應的.dts能夠include這個.dtsi。spa

 

譬如,對於RK3288而言, rk3288.dtsi就被rk3288-chrome.dts所引用,rk3288-chrome.dts有以下一行:#include「rk3288.dtsi」。

 

再如rtd1195, 在 rtd-119x-nas.dts中就包含了/include/ "rtd-119x.dtsi"。

 

固然,和C語言的頭文件相似,.dtsi也能夠include其餘的.dtsi,譬如幾乎全部的ARM SoC的.dtsi都引用了skeleton.dtsi,即#include"skeleton.dtsi「

或者 /include/ "skeleton.dtsi"

 

正常狀況下全部的dts文件以及dtsi文件都含有一個根節點」/」, 這樣include以後就會有不少個根節點。按理說 device tree既然是一個樹,那麼其只能有一個根節點,全部其餘的節點都是派生於根節點的child node。

其實Device Tree Compiler會對DTS的node進行合併,最終生成的DTB中只有一個 root  node。

device tree的基本單元是node。這些node被組織成樹狀結構,除了root node,每一個node都只有一個parent。一個device tree文件中只能有一個root node。每一個node中包含了若干的property/value來描述該node的一些特性。

每一個node用節點名字(node name)標識,節點名字的格式是node-name@unit-address。

若是該node沒有reg屬性(後面會描述這個property),那麼該節點名字中必須不能包括@和unit-address。unit-address的具體格式是和設備掛在那個bus上相關。例如對於cpu,其unit-address就是從0開始編址,以此加一。

而具體的設備,例如以太網控制器,其unit-address就是寄存器地址。root node的node name是肯定的,必須是「/」。

在一個樹狀結構的device tree中,如何引用一個node呢?要想惟一指定一個node必須使用full path,例如/node-name-1/node-name-2/node-name-N。 

3、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-property = <1 2 3 4>; /* each number (cell) is a uint32 */  

            child-node1 {

            };

        };

    };

 

上述.dts文件並無什麼真實的用途,但它基本表示了一個Device Tree源文件的結構:
一、1個root結點"/";


二、root結點下面含一系列子結點,本例中爲"node1"和 "node2";


三、結點"node1"下又含有一系列子結點,本例中爲"child-node1"和 "child-node2";


四、各結點都有一系列屬性。這些屬性可能爲空,如"an-empty-property";可能爲字符串,如"a-string-property";可能爲字符串數組,如"a-string-list-property";可能爲Cells(由u32整數組成),如"second-child-property",可能爲二進制數,如"a-byte-data-property"。

 

下面以一個最簡單的machine爲例來看如何寫一個.dts文件。假設此machine的配置以下:
一、1個雙核ARM Cortex-A9 32位處理器;


二、ARM的local bus上的內存映射區域分佈了2個串口(分別位於0x101F1000 和 0x101F2000)、GPIO控制器(位於0x101F3000)、SPI控制器(位於0x10115000)、中斷控制器(位於0x10140000)和一個external bus橋;


三、External bus橋上又鏈接了SMC SMC91111 Ethernet(位於0x10100000)、I2C控制器(位於0x10160000)、64MB NOR Flash(位於0x30000000);


四、External bus橋上鍊接的I2C控制器所對應的I2C總線上又鏈接了Maxim DS1338實時鐘(I2C地址爲0x58)。


其對應的.dts文件爲:

    {

        compatible = "acme,coyotes-revenge";

    #address-cells = <1>;  

    #size-cells = <1>;  

        interrupt-parent = <&intc>;


        cpus {

    #address-cells = <1>;  

    #size-cells = <0>;  

            cpu@0 {

                compatible = "arm,cortex-a9";

                reg = <0>;

            };

            cpu@1 {

                compatible = "arm,cortex-a9";

                reg = <1>;

            };

        };


        serial@101f0000 {

            compatible = "arm,pl011";

            reg = <0x101f0000 0x1000 >;

            interrupts = < 1 0 >;

        };


        serial@101f2000 {

            compatible = "arm,pl011";

            reg = <0x101f2000 0x1000 >;

            interrupts = < 2 0 >;

        };




    gpio@101f3000 {

            compatible = "arm,pl061";

            reg = <0x101f3000 0x1000  

    0x101f4000 0x0010>;

            interrupts = < 3 0 >;

        };


        intc: interrupt-controller@10140000 {

            compatible = "arm,pl190";

            reg = <0x10140000 0x1000 >;

            interrupt-controller;

    #interrupt-cells = <2>;  

        };


        spi@10115000 {

            compatible = "arm,pl022";

            reg = <0x10115000 0x1000 >;

            interrupts = < 4 0 >;

        };




    external-bus {

    #address-cells = <2>  

    #size-cells = <1>;  

            ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet  

    1 0  0x10160000   0x10000     // Chipselect 2, i2c controller  

    2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash  


            ethernet@0,0 {

                compatible = "smc,smc91c111";

                reg = <0 0 0x1000>;

                interrupts = < 5 2 >;

            };


            i2c@1,0 {

                compatible = "acme,a1234-i2c-bus";

    #address-cells = <1>;  

    #size-cells = <0>;  

                reg = <1 0 0x1000>;

                rtc@58 {

                    compatible = "maxim,ds1338";

                    reg = <58>;

                    interrupts = < 7 3 >;

                };

            };


            flash@2,0 {

                compatible = "samsung,k8f1315ebm", "cfi-flash";

                reg = <2 0 0x4000000>;

            };

        };

    };

 


上述.dts文件中, root結點"/"的compatible 屬性compatible = "acme,coyotes-revenge";定義了系統的名稱,它的組織形式爲:<manufacturer>,<model>。

 

Linux內核透過root結點"/"的compatible 屬性便可判斷它啓動的是什麼machine。

 

在.dts文件的每一個設備,都有一個compatible屬性,compatible屬性用於驅動和設備的綁定。compatible 屬性是一個字符串的列表,列表中的第一個字符串表徵告終點表明的確切設備,形式爲"<manufacturer>,<model>",其後的字符串表徵可兼容的其餘設備。能夠說前面的是特指,後面的則涵蓋更廣的範圍。

如在arch/arm/boot/dts/vexpress-v2m.dtsi中的Flash結點:

    flash@0,00000000 {

        compatible = "arm,vexpress-flash", "cfi-flash";

        reg = <0 0x00000000 0x04000000>,

        <1 0x00000000 0x04000000>;

        bank-width = <4>;

    };

 


compatible屬性的第2個字符串"cfi-flash"明顯比第1個字符串"arm,vexpress-flash"涵蓋的範圍更廣。

 

接下來root結點"/"的cpus子結點下面又包含2個cpu子結點,描述了此machine上的2個CPU,而且兩者的compatible 屬性爲"arm,cortex-a9"。


注意:cpus和cpus的2個cpu子結點的命名,它們遵循的組織形式爲:<name>[@<unit-address>],<>中的內容是必選項,[]中的則爲可選項。name是一個ASCII字符串,用於描述結點對應的設備類型,如3com Ethernet適配器對應的結點name宜爲ethernet,而不是3com509。

若是一個結點描述的設備有地址,則應該給出@unit-address。多個相同類型設備結點的name能夠同樣,只要unit-address不一樣便可,如本例中含有cpu@0、cpu@1以及serial@101f0000與serial@101f2000這樣的同名結點。設備的unit-address地址也常常在其對應結點的reg屬性中給出。

reg的組織形式爲reg = <address1 length1 [address2 length2][address3 length3] ... >,其中的每一組addresslength代表了設備使用的一個地址範圍。address爲1個或多個32位的整型(即cell),而length則爲cell的列表或者爲空(若#size-cells = 0)。

address和length字段是可變長的,父結點的#address-cells和#size-cells分別決定了子結點的reg屬性的address和length字段的長度。

在本例中,root結點的#address-cells = <1>;和#size-cells =<1>;決定了serial、gpio、spi等結點的address和length字段的長度分別爲1。

cpus 結點的#address-cells= <1>,和#size-cells =<0>。決定了2個cpu子結點的address爲1,而length爲空,因而造成了2個cpu的reg =<0>, 和reg =<1>;

external-bus結點的#address-cells= <2>和#size-cells =<1>;決定了其下的ethernet、i2c、flash的reg字段形如reg = <0 00x1000>; reg = <1 00x1000>;和reg = <2 00x4000000>;

其中,address字段長度爲0,開始的第一個cell(0、一、2)是對應的片選,第2個cell(0,0,0)是相對該片選的基地址,第3個cell(0x1000、0x1000、0x4000000)爲length。

特別要留意的是i2c結點中定義的 #address-cells = <1>,和#size-cells =<0>; 又做用到了I2C總線上鏈接的RTC,它的address字段爲0x58,是設備的I2C地址。

root結點的子結點描述的是CPU的視圖,所以root子結點的address區域就直接位於CPU的memory區域。可是,通過總線橋後的address每每須要通過轉換才能對應的CPU的memory映射。

external-bus的ranges屬性定義了通過external-bus橋後的地址範圍如何映射到CPU的memory區域。

  1. ranges = <0 0  0x10100000   0x10000 // Chipselect 1, Ethernet  

  2. 1 0  0x10160000   0x10000 // Chipselect 2, i2c controller  

  3. 2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash

ranges是地址轉換表,其中的每一個項目是一個子地址、父地址以及在子地址空間的大小的映射。映射表中的子地址、父地址分別採用子地址空間的#address-cells和父地址空間的#address-cells大小。

 

對於本例而言,子地址空間的#address-cells爲2,父地址空間的#address-cells值爲1,所以0 0  0x10100000   0x10000的前2個cell爲external-bus後片選0上偏移0,第3個cell表示external-bus後片選0上偏移0的地址空間被映射到CPU的0x10100000位置,第4個cell表示映射的大小爲0x10000。ranges的後面2個項目的含義能夠類推。

Device Tree中還能夠中斷鏈接信息,對於中斷控制器而言,它提供以下屬性:
interrupt-controller– 這個屬性爲空,中斷控制器應該加上此屬性代表本身的身份;
#interrupt-cells– 與#address-cells 和 #size-cells類似,它代表鏈接此中斷控制器的設備的interrupts屬性的cell大小。


在整個Device Tree中,與中斷相關的屬性還包括:
interrupt-parent– 設備結點透過它來指定它所依附的中斷控制器的phandle,當結點沒有指定interrupt-parent時,則從父級結點繼承。

對於本例而言,root結點指定了interrupt-parent= <&intc>;其對應於intc: interrupt-controller@10140000,而root結點的子結點並未指定interrupt-parent,所以它們都繼承了intc,即位於0x10140000的中斷控制器。

interrupts – 用到了中斷的設備結點透過它指定中斷號、觸發方法等,具體這個屬性含有多少個cell,由它依附的中斷控制器結點的#interrupt-cells屬性決定。而具體每一個cell又是什麼含義,通常由驅動的實現決定,並且也會在Device Tree的binding文檔中說明。

譬如,對於ARM GIC中斷控制器而言,#interrupt-cells爲3,它3個cell的具體含義kernel/Documentation/devicetree/bindings/arm/gic.txt就有以下文字說明:

 

PPI(Private peripheral interrupt)    SPI(Shared peripheral interrupt)

一個設備還可能用到多箇中斷號。對於ARM GIC而言,若某設備使用了SPI的16八、169號2箇中斷,而言都是高電平觸發,則該設備結點的interrupts屬性可定義爲:interrupts =<0 168 4>, <0 169 4>; 

4、dts引發BSP和driver的變動  

沒有使用dts以前的BSP和driver 

 

 



 

使用dts以後的driver

 

 

 

針對上面的dts,注意如下幾點:

(1)、rtk_gpio_ctl_mlk這個是node的名字,本身能夠隨便定義,固然最好是見名知意,能夠經過驅動程序打印當前使用的設備樹節點

printk(「now dts node name is %s\n",pdev->dev.of_node->name);

(2). compatible選項是用來和驅動程序中的of_match_table指針所指向的of_device_id結構裏的compatible字段匹配的,只有dts裏的compatible字段的名字和驅動程序中of_device_id裏的compatible字段的名字同樣,驅動程序才能進入probe函數。

(3)、對於gpios這個字段,首先&rtk_iso_gpio指明瞭這個gpio是鏈接到的是rtk_iso_gpio, 接着那個8是gpio number偏移量,它是以rtk_iso_gpiobase爲基準的, 緊接着那個0說明目前配置的gpio number 是設置成輸入input, 若是是1就是設置成輸出output,最後一個字段1是指定這個gpio 默認爲高電平,若是是0則是指定這個gpio默認爲低電平。

(4)、若是驅動裏面只是利用compatible字段進行匹配進入probe函數,那麼gpios 能夠不須要,可是若是驅動程序裏面是採用設備樹相關的方法進行操做獲取gpio  number, 那麼gpios這個字段必須使用。 gpios這個字段是由of_get_gpio_flags函數默認指定的name。

獲取gpio number的函數以下:

of_get_named_gpio_flags()

of_get_gpio_flags()    

註冊i2c_board_info,指定IRQ等板級信息。

形如:

    static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {

    {

    I2C_BOARD_INFO("tlv320aic23", 0x1a),

    }, {

    I2C_BOARD_INFO("fm3130", 0x68),

    }, {

    I2C_BOARD_INFO("24c64", 0x50),

    }

    };

 

之類的i2c_board_info代碼,目前再也不須要出現,如今只須要把tlv320aic2三、fm3130、24c64這些設備結點填充做爲相應的I2C controller結點的子結點便可,相似於前面的

    i2c@1,0 {

        compatible = "acme,a1234-i2c-bus";

        …

        rtc@58 {

            compatible = "maxim,ds1338";

            reg = <58>;

            interrupts = < 7 3 >;

        };

    };

 

Device Tree中的I2C client會透過I2C host驅動的probe()函數中調用of_i2c_register_devices(&i2c_dev->adapter); 而後被自動展開。

 

5、常見的DTS 函數

Linux內核中目前DTS相關的函數都是以of_前綴開頭的,它們的實現位於內核源碼的drivers/of下面

 void __iomem*of_iomap(struct device_node *node, int index)

經過設備結點直接進行設備內存區間的 ioremap(),index是內存段的索引。若設備結點的reg屬性有多段,可經過index標示要ioremap的是哪一段,只有1段的狀況,index爲0。

採用Device Tree後,大量的設備驅動經過of_iomap()進行映射,而再也不經過傳統的ioremap。

  1. int of_get_named_gpio_flags(struct device_node *np,const char *propname,

  2. int index, enum of_gpio_flags *flags)

  3.  

  4. static inline int of_get_gpio_flags(structdevice_node *np, int index,

  5. enum of_gpio_flags *flags)

  6. {

  7. return of_get_named_gpio_flags(np, "gpios", index,flags);

  8. }

 

從設備樹中讀取相關GPIO的配置編號和標誌,返回值爲 gpio number。

 

6、DTC (device tree compiler)

將.dts編譯爲.dtb的工具。DTC的源代碼位於內核的scripts/dtc目錄,在Linux內核使能了Device Tree的狀況下,編譯內核的時候主機工具dtc會被編譯出來,對應scripts/dtc/Makefile中的「hostprogs-y := dtc」這一hostprogs編譯target。


在Linux內核的arch/arm/boot/dts/Makefile中,描述了當某種SoC被選中後,哪些.dtb文件會被編譯出來,如與VEXPRESS對應的.dtb包括:

  1. dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \

  2. vexpress-v2p-ca9.dtb \

  3. vexpress-v2p-ca15-tc1.dtb \

  4. vexpress-v2p-ca15_a7.dtb \

  5. xenvm-4.2.dtb

當咱們在Linux內核下運行make dtbs時,若以前選擇了ARCH_VEXPRESS,上述.dtb都會由對應的.dts編譯出來。由於arch/arm/Makefile中含有一個dtbs編譯target項目。固然也能夠單獨編譯Device Tree文件。命令由讀者自行去找。

相關文章
相關標籤/搜索