linux 設備樹

linux 設備樹


參考地址
http://blog.csdn.net/green1900/article/details/45646095
http://www.cnblogs.com/xiaojiang1025/p/6131381.html
http://blog.csdn.net/21cnbao/article/details/8457546


1.爲什麼要使用設備樹(Device Tree)?

在以前的內核源碼中,存在大量對板級細節信息描述的代碼,這些代碼充斥在/arch/arm/plat-xxx和/arch/arm/mach-xxx目錄,對內核而言這些platform設備、resource、i2c_board_info、spi_board_info以及各種硬件的platform_data絕大多數純屬垃圾冗餘代碼。爲了解決這一問題,ARM內核版本3.x之後引入了原先在Power PC等其他體系架構已經使用的Flattened Device Tree。DTS不是arm的專利

在使用了設備樹後,對於同一SOC的不同主板,只需更換設備樹文件.dtb即可實現不同主板的無差異支持,而無需更換內核文件。


2.設備樹的的組成和結構

設備樹可以描述的信息包括了
1. CPU的數量和類別、
2. 內存基地址和大小、
3. 總線和橋、
4. 外設連接、
5. 中斷控制器和中斷使用情況、
6. GPIO控制器和GPIO使用情況、
7. Clock控制器和Clock使用情況。
需要注意的是,設備樹對於可熱插拔的熱備不進行具體描述,它只描述用於控制該熱插拔設備的控制器

2.1設備樹的組成

設備樹包含了DTC(device tree compiler) , DTS(device tree resource) 和 DTB(device tree blob),簡單來說,dts是源碼,dtc是編譯器,dtb是生成的可執行文件
此處輸入圖片的描述

2.1.1 DTS和DTSI

.dts和.dtsi是一種ASCII文本的設備樹描述,此文本格式非常適合人們閱讀,基本上,一個.dts對應一種ARM設備,放在arch/arm/boot/dts目錄,由於一個soc對應好多個不同的開發板,每個開發板有一個.dts,所以這些dts勢必有共同部分,爲了減少代碼的屯餘,設備樹將這些共同部分提煉保存在dtsi中,供不同的dts使用,dtsi文件類似於c語言的頭文件
  • 1
  • 2

2.1.2 DTC

DTC爲編譯工具,它可以將.dts文件編譯成.dtb文件。DTC的源碼位於內核的scripts/dtc目錄,內核選中CONFIG_OF,編譯內核的時候,主機可執行程序DTC就會被編譯出來

2.1.3 DTB

DTB設備由DTC編譯後的二進制格式的設備樹描述,可以由linux內核解析,uboot這樣的bootloader也可以識別.dtb,有兩種使用方式,一種是bootloader啓動內核過程中會先讀取dtb到文件中;第二種是把dtb和zImage打包在一起做成一個印象文件,firefly-3399就是採用這種方式,打包生成了boot.img

2.1.4 綁定(bingding)

對於Device Tree中的結點和屬性具體是如何來描述設備的硬件細節的,一般需要文檔來進行講解,文檔的後綴名一般爲.txt。這些文檔位於內核的Documentation/devicetree/bindings目錄,其下又分爲很多子目錄

2.1.5 Bootloader 使用dtb

在Uboot中,可以從NAND、SD或者TFTP等任意介質將.dtb讀入內存,假設.dtb放入的內存地址爲0x71000000,之後可在Uboot運行命令fdt addr命令設置.dtb的地址,如:
U-Boot> fdt addr 0x71000000
fdt的其他命令就變地可以使用,如fdt resize、fdt print等
對於ARM來講,可以透過bootz kernel_addr initrd_address dtb_address的命令來啓動內核,即dtb_address作爲bootz或者bootm的最後一次參數,第一個參數爲內核映像的地址,第二個參數爲initrd的地址,若不存在initrd,可以用 -代替,第三個就是dtb地址

2.2設備樹框架

設備樹用樹狀結構描述設備信息,它有以下幾種特性
1. 每個設備樹文件都有一個根節點,每個設備都是一個節點。
2. 節點間可以嵌套,形成父子關係,這樣就可以方便的描述設備間的關係。
3. 每個設備的屬性都用一組key-value對(鍵值對)來描述。
4. 每個屬性的描述用;結束


3. 設備樹語法

設備樹是一顆樹,書上的每個節點由節點和屬性組成,屬性是鍵值對

下面這個是rk3399-fpga.dts

#include "rk3399.dtsi"  //包含了公共部分
/ {
        model = "Rockchip RK3399 FPGA Board";
        compatible = "rockchip,fpga", "rockchip,rk3399"; //根節點兼容性分析,下面具體分析
        chosen {
                bootargs = "init=/init console=uart,mmio32,0xff1a0000";
        };
        memory@00000000 { //子節點  memory@00000000節點名
                device_type = "memory";
                reg = <0x0 0x00000000 0x0 0x20000000>;
        };
};
&uart2 { //使用了引用
        status = "okay";
        clocks = <&xin24m>, <&xin24m>;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.1根節點兼容性

compatible = "rockchip,fpga", "rockchip,rk3399";
  • 1
上面是根節點的兼容屬性,定義了整個系統(設備級別)的名稱,通過這個屬性就可以判斷出它啓動的是什麼設備。它的組織形式是&lt;manufacture&gt;&lt;model&gt;,在實際中一般包括兩個或兩個以上的兼容字符串,上面第一個是"rockchip,fpga",第二個是"rockchip,rk3399",我們來看第二個,manufacture是板子級別的名字,“rockchip”代表的是瑞芯微公司,model是芯片級別的,“rk3399”是瑞芯微公司一個soc的名稱
  • 1
  • 2

我們從源碼中找出rk3399的兩個dts,可以看出第一個兼容字符串的model不同,第二個完全相同

rk3399-firefly-linux.dts
compatible = "rockchip,rk3399-firefly-linux", "rockchip,rk3399";
rk3399-fpga.dts
compatible = "rockchip,fpga", "rockchip,rk3399";
  • 1
  • 2
  • 3
  • 4

3.2節點名

理論個節點名只要是長度不超過31個字符的ASCII字符串即可,Linux內核還約定設備名應寫成形如[@]的形式,其中name就是設備名,最長可以是31個字符長度。unit_address一般是設備地址,用來唯一標識一個節點
Linux中的設備樹還包括幾個特殊的節點,比如chosen,chosen節點不描述一個真實設備,而是用於firmware傳遞一些數據給OS,比如bootloader傳遞內核啓動參數給內核

chosen{
    bootargs = "console=ttySAC2,115200";
    stdout-path=&serial_2;
};
  • 1
  • 2
  • 3
  • 4

3.3引用

當我們找一個節點的時候,我們必須書寫完整的節點路徑,這樣當一個節點嵌套比較深的時候就不是很方便,所以,設備樹允許我們用下面的形式爲節點標註引用(起別名),藉以省去冗長的路徑。這樣就可以實現類似函數調用的效果

3.KEY

在設備樹中,鍵值對是描述屬性的方式,比如,Linux驅動中可以通過設備節點中的”compatible”這個屬性查找設備節點
inux設備樹語法中定義了一些具有規範意義的屬性,包括:compatible, address, interrupt等,這些信息能夠在內核初始化找到節點的時候,自動解析生成相應的設備信息。此外,還有一些Linux內核定義好的,一類設備通用的有默認意義的屬性,這些屬性一般不能被內核自動解析生成相應的設備信息,但是內核已經編寫的相應的解析提取函數,常見的有 “mac_addr”,”gpio”,”clock”,”power”。”regulator” 等等。

3.1.compatible

設備節點中對應的節點信息已經被內核構造成struct platform_device。驅動可以通過相應的函數從中提取信息。主要有三種方法提取信息

    1、compatible屬性是用來查找節點
    2、通過節點名查找指定節點
    3、節點路徑查找指定節點
  • 1
  • 2
  • 3

看一個使用compatible提取屬性的例子

#dts
    gpio_demo: gpio_demo {
        status = "okay";
        compatible = "firefly,rk3399-gpio";                  
    };
#驅動代碼
static struct of_device_id firefly_match_table[] = {
    { .compatible = "firefly,rk3399-gpio",}, //完全相同
    {}, //最後一個成員一定是空,因爲相關的操作API會讀取這個數組直到遇到一個空。
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.2address

  • #address-cells,用來描述子節點”reg”屬性的地址表中用來描述首地址的cell的數量
  • #size-cells,用來描述子節點”reg”屬性的地址表中用來描述地址長度的cell的數量。
        pinctrl: pinctrl {
                compatible = "rockchip,rk3399-pinctrl";
                #address-cells = <0x2>;
                #size-cells = <0x2>;
                gpio0: gpio0@ff720000 {
                        compatible = "rockchip,gpio-bank";
                        reg = <0x0 0xff720000 0x0 0x100>;
                        //前兩個數字表示一個地址0x0 0xff720000
                        //後兩個數字表示一個地址跨度 0x100
                };
            ...
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.3interrupts

一個計算機系統中大量設備都是通過中斷請求CPU服務的,所以設備節點中就需要在指定中斷

  • interrupt-controller 一個空屬性用來聲明這個node接收中斷信號,即這個node是一箇中斷控制器
  • #interrupt-cells,是中斷控制器節點的屬性,用來標識這個控制器需要幾個單位做中斷描述符,用來描述子節點中”interrupts”屬性使用了父節點中的interrupts屬性的具體的哪個值。一般,如果父節點的該屬性的值是3,則子節點的interrupts一個cell的三個32bits整數值分別爲:<中斷域 中斷 觸發方式>,如果父節點的該屬性是2,則是<中斷 觸發方式>
  • interrupt-parent,標識此設備節點屬於哪一個中斷控制器,如果沒有設置這個屬性,會自動依附父節點的
  • interrupts,一箇中斷標識符列表,表示每一箇中斷輸出信號

3.4gpio

  • gpio-controller,用來說明該節點描述的是一個gpio控制器
  • #gpio-cells,用來描述gpio使用節點的屬性一個cell的內容,即 `屬性 = <&引用GPIO節點別名 GPIO標號 工作模式>
firefly-gpio = <&gpio0 12 GPIO_ACTIVE_HIGH>;          /* GPIO0_B4 */
firefly-irq-gpio = <&gpio4 29 IRQ_TYPE_EDGE_RISING>;  /* GPIO4_D5 */  
  • 1
  • 2

4.DTB的加載過程

參考地址
http://blog.csdn.net/green1900/article/details/45646095
此處輸入圖片的描述
http://blog.csdn.net/lichengtongxiazai/article/details/38941913
此處輸入圖片的描述

總的歸納爲:

① kernel入口處獲取到uboot傳過來的.dtb鏡像的基地址

② 通過early_init_dt_scan()函數來獲取kernel初始化時需要的bootargs和cmd_line等系統引導參數。

③ 調用unflatten_device_tree函數來解析dtb文件,構建一個由device_node結構連接而成的單向鏈表,並使用全局變量of_allnodes保存這個鏈表的頭指針。

④ 內核調用OF的API接口,獲取of_allnodes鏈表信息來初始化內核其他子系統、設備等。


5.API調用

#來查找在dtb中的根節點
unsigned long __init of_get_flat_dt_root(void)

# 根據deice_node結構的full_name參數,在全局鏈表of_allnodes中,查找合適的device_node
struct device_node *of_find_node_by_path(const char *path)

#若from=NULL,則在全局鏈表of_allnodes中根據name查找合適的device_node
struct device_node *of_find_node_by_name(struct device_node *from,const char *name)

#根據設備類型查找相應的device_node
struct device_node *of_find_node_by_type(struct device_node *from,const char *type)

# 根據compatible字符串查找device_node
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)

#根據節點屬性的name查找device_node
struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name)

#根據compat參數與device node的compatible匹配,返回匹配度
int of_device_is_compatible(const struct device_node *device,const char *compat)

#獲得父節點的device node
struct device_node *of_get_parent(const struct device_node *node)

#讀取該設備的第index個irq號
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

#讀取該設備的第index個irq號,並填充一個irq資源結構體
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)

#獲取該設備的irq個數
int of_irq_count(struct device_node *dev)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

發佈了8 篇原創文章 · 獲贊 5 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章