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
上面是根節點的兼容屬性,定義了整個系統(設備級別)的名稱,通過這個屬性就可以判斷出它啓動的是什麼設備。它的組織形式是<manufacture><model>,在實際中一般包括兩個或兩個以上的兼容字符串,上面第一個是"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