Device Tree常用方法解析
http://blog.csdn.net/airk000/article/details/21345159
Device Tree常用方法解析
Device Tree在Linux內核驅動中的使用源於2011年3月17日Linus Torvalds在ARM Linux郵件列表中的一封郵件,他宣稱“this whole ARM thing is a f*cking pain in the ass”,並提倡學習PowerPC等其他架構已經成熟使用的Device Tree技術。自此,Device Tree正式進入ARM社區的視野中。
1. 作用
Device Tree是一種用來描述硬件的數據結構,類似板級描述語言,起源於OpenFirmware(OF)。在目前廣泛使用的Linux kernel 2.6.x版本中,對於不同平臺、不同硬件,往往存在著大量的不同的、移植性差的板級描述代碼,以達到對這些不同平臺和不同硬件特殊適配的需求。但是過多的平臺、過的的不同硬件導致了這樣的代碼越來越多,最終引發了Linux創始人Linus的不滿,以及強烈呼籲改變。Device Tree的引入給驅動適配帶來了很大的方便,一套完整的Device Tree可以將一個PCB擺在你眼前。Device Tree可以描述CPU,可以描述時鐘、中斷控制器、IO控制器、SPI總線控制器、I2C控制器、存儲設備等任何現有驅動單位。對具體器件能夠描述到使用哪個中斷,內存映射空間是多少等等。
2. 基本數據格式
Device Tree由節點和屬性構成。屬性為key-value對,節點包括了各種屬性,也可以包含子節點。下邊列舉一個簡單的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 { }; }; };
這個文件實際上沒有任何意義,但卻包含了基本所有要素:
- 1 唯一的根節點 “/”
- 2 一些節點:node1 node2
- 3 子節點 node1的子節點child-node1和child-node2
- 4 一群分散的屬性
屬性都是簡單的key-value對,其中value也可以是空的或包含任意的byte流。以下是一些屬性的基本數據結構:
-
1 雙引號包含的字符信息
string-property = "a string";
-
2 cells單位信息是32位無符號整型數據
cell-property = <0xFF01 412 0x12341283>;
-
3 二進制數據流
binary-property = [0x01 0x02 0x03 0x04];
-
4 混合數據用逗號隔開
mixed-property = "a string", [0x01 0x02 0x03 0x04], <0xFF01 412 0x12341283>;
-
5 字符列表
string-list = "string test1", "string test2";
3. 一些基本概念
- 每個完整的dts文件必須擁有一個根節點
- dtsi文件一般為通用文件(類似C語言的頭文件),可被其他文件include
後邊的名字涵蓋的範圍更加廣泛,如果可以匹配到,同樣會以這個dts為基礎進行初始化並啟動。 - 父節點名應該取類型名,而不是IC名。節點名的命名規則一般是 [name]@[address],也可以只有name而沒有@之後的內容,但是要確保name不能重名。如果加了@以及地址,那麽name可以相同,只要address不同即可。
- 每一個設備節點都要有一個compatible屬性
-
compatible的內容是用來匹配驅動的,組成方式為"[manufacturer], [model]",加入廠商名是為了避免重名。有的時候後邊還會跟一個名字,如:
compatible = "acme,coyotes-revenge", "acmd-board";
4. 工作方式
a. 地址
設備的地址特性根據一下幾個屬性來控制:
- reg
- #address-cells
- #size-cells
reg意為region,區域。格式為:
reg = <address1 length1 [address2 length2] [address3 length3]>;
父類的address-cells和size-cells決定了子類的相關屬性要包含多少個cell,如果子節點有特殊需求的話,可以自己再定義,這樣就可以擺脫父節點的控制。
address-cells決定了address1/2/3包含幾個cell,size-cells決定了length1/2/3包含了幾個cell。本地模塊例如:
spi@10115000 {
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
};
位於0x10115000的SPI設備申請地址空間,起始地址為0x10115000,長度為0x1000,即屬於這個SPI設備的地址範圍是0x10115000~0x10116000。
實際應用中,有另外一種情況,就是通過外部芯片片選激活模塊。例如,掛載在外部總線上,需要通過片選線工作的一些模塊:
external-bus {
#address-cells = <2>
#size-cells = <1>;
ethernet@0,0 {
compatible = "smc,smc91c111";
reg = <0 0 0x1000>;
};
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>;
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};
external-bus使用兩個cell來描述地址,一個是片選序號,另一個是片選序號上的偏移量。而地址空間長度依然用一個cell來描述。所以以上的子設備們都需要3個cell來描述地址空間屬性——片選、偏移量、地址長度。在上個例子中,有一個例外,就是i2c控制器模塊下的rtc模塊。因為I2C設備只是被分配在一個地址上,不需要其他任何空間,所以只需要一個address的cell就可以描述完整,不需要size-cells。
當需要描述的設備不是本地設備時,就需要描述一個從設備地址空間到CPU地址空間的映射關系,這裏就需要用到ranges屬性。還是以上邊的external-bus舉例:
#address-cells = <1>;
#size-cells = <1>;
...
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
};
ranges屬性為一個地址轉換表。表中的每一行都包含了子地址、父地址、在自地址空間內的區域大小。他們的大小(包含的cell)分別由子節點的address-cells的值、父節點的address-cells的值和子節點的size-cells來決定。以第一行為例:
- 0 0 兩個cell,由子節點external-bus的address-cells=<2>決定;
- 0x10100000 一個cell,由父節點的address-cells=<1>決定;
- 0x10000 一個cell,由子節點external-bus的size-cells=<1>決定。
最終第一行說明的意思就是:片選0,偏移0(選中了網卡),被映射到CPU地址空間的0x10100000~0x10110000中,地址長度為0x10000。
b. 中斷
描述中斷連接需要四個屬性:
1. interrupt-controller 一個空屬性用來聲明這個node接收中斷信號;
2. #interrupt-cells 這是中斷控制器節點的屬性,用來標識這個控制器需要幾個單位做中斷描述符;
3. interrupt-parent 標識此設備節點屬於哪一個中斷控制器,如果沒有設置這個屬性,會自動依附父節點的;
4. interrupts 一個中斷標識符列表,表示每一個中斷輸出信號。
如果有兩個,第一個是中斷號,第二個是中斷類型,如高電平、低電平、邊緣觸發等觸發特性。對於給定的中斷控制器,應該仔細閱讀相關文檔來確定其中斷標識該如何解析。
c. 其他
除了以上規則外,也可以自己加一些自定義的屬性和子節點,但是一定要符合以下的幾個規則:
- 新的設備屬性一定要以廠家名字做前綴,這樣就可以避免他們會和當前的標準屬性存在命名沖突問題;
- 新加的屬性具體含義以及子節點必須加以文檔描述,這樣設備驅動開發者就知道怎麽解釋這些數據了。描述文檔中必須特別說明compatible的value的意義,應該有什麽屬性,可以有哪個(些)子節點,以及這代表了什麽設備。每個獨立的compatible都應該由單獨的解釋。
- 新添加的這些要發送到[email protected]郵件列表中進行review,並且檢查是否會在將來引發其他的問題。
5. 進階例子
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;
#address-cells = <3>
#size-cells = <2>;
ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000
0x02000000 0 0xa0000000 0xa0000000 0 0x10000000
0x01000000 0 0x00000000 0xb0000000 0 0x01000000>;
};
像之前描述過的本地總線一樣,PCI地址空間與CPU地址空間是完全分離的,所以這裏需要通過定義ranges屬性進行地址轉化。
#address-cells定義PCI使用3個cell,並且PCI的地址範圍通過兩個單位就可以解讀。所以,首先的問題就是,為什麽需要用3個32位的cell來描述一個PCI地址。
這三個cell分別代表物理地址高位、中位、低位:
- 1 phys.high cell : npt000ss bbbbbbbb dddddfff rrrrrrrr
- 2 phys.mid cell : hhhhhhh hhhhhhhh hhhhhhhh hhhhhhh
- 3 phys.low cell : llllllll llllllll llllllll llllllll
PCI地址為64位寬度,編碼在phys.mid和phys.low中。真正重要的東西在於phys.high這一位空間中:
n:代表重申請空間標誌(這裏沒有使用)
p:代表預讀空間(緩存)標誌
t:別名地址標誌(這裏沒有使用)
ss:空間代碼
00: 設置空間
01:IO空間
10:32位存儲空間
11:64位存儲空間
bbbbbbbb: PCI總線號。PCI有可能是層次性架構,所以我們可能需要區分一些子-總線
ddddd:設備號,通常由初始化設備選擇信號IDSEL連接時申請。
fff:功能序號,有些多功能PCI設備可能用到。
rrrrrrrr:註冊號,在設置周期使用。
ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000
0x02000000 0 0xa0000000 0xa0000000 0 0x10000000
0x01000000 0 0x00000000 0xb0000000 0 0x01000000>;
回頭再看這個ranges分表代表了什麽。父節點address-cells為1,子節點address-cells為3, 子節點size-cells為2。則第一行可以這樣劃分:
0x42000000 0 0x80000000 子節點地址| 0x80000000 父節點地址| 0 0x20000000 地址空間長度|
0x42000000為phys.high,第一位為01000010,則p為1,ss為10,即申請32位存儲空間為緩存空間。phys.mid為0,phys.low為0x80000000,他們共同組成了PCI地址,即表示從PCI總線的0x80000000地址處申請出一個32位的存儲空間作為緩存。後邊的那個cell 0x80000000 0 0x20000000代表到CPU空間後的參數,申請的地址被映射到CPU空間的0x80000000地址處,大小共計0x20000000(512MB)。
相關資料及引用:
http://blog.csdn.net/21cnbao/article/details/8457546
http://devicetree.org/Device_Tree_Usage
Device Tree常用方法解析