基本概念
________________________________________________
爲了理解怎樣使用設備樹,我們從一個樣品機開始並且搭建設備樹來一步步描述它
樣品機
考慮下面的假想機器(粗略地基於ARM Versatile),"Acme"製造,命名 “Coyote's Revenge":
- 32位 ARM CPU單核
- PLB粘附在內存映射串口上,spi總線控制器,i2c控制器,中斷控制器以及外部總線橋
- 256MB SDRAM基址從0開始
- 2個串口,基址從0x101F1000,0X101F2000開始
- GPIO控制器,基址從0x101F3000開始
- SPI控制器,基址從0x10170000並且總線上掛載着下列設備:
- 外部總線橋並且總線上掛載着下列設備:
- SMC SMC91111以太網設備連接在基址從0x10100000開始的外部總線上
- i2c控制器,基址從0x10160000開始並且總線上掛載着如下設備:
- Maxim DS1338實時時鐘。該器件響應0x58的從機地址。
- 64MB NOR flash,基址從0x30000000開始
初始結構
第一步是爲機器制定框架結構。下面是一個合法的設備樹所需的最小結構。在這個節點,你想想要能夠唯一地識別該機器
/ {
compatible = "acme,coyotes-revenge";
};
compatible制定系統的名稱。它包含"<manufacture>,<model>"格式的字符串。準確地確定器件型號是非常重要的,並且我們需要包含廠商的名字來避免名字空間衝突。因爲操作系統會使用compatible這個值來決定怎樣在這個機器上運行,所以在這個屬性中放入正確的值是非常重要的。
理論上來說,compatible是一個OS需要唯一地識別機器所需要的唯一數據。如果所有機器的細節都是寫死的,那麼OS可以在頂層compatible屬性中專門查找"acme,coyotes-revenge"。
CPUs
下一步是描述每個CPU。一個命名爲"cpus"的容器節點跟有對應每個CPU的子節點。在這個例子中,系統是一個雙核ARM Cortex A9的系統。
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
};
每個CPU節點中的compatible屬性是以<manufacturer>,<model>的格式確定CPU型號的字符串,就像頂層的compatible屬性那樣。
更多的屬性會在稍後添加到cpu節點中,但是我們首先需要討論更多基本的概念。
節點名字
我們值得花一段時間討論命名習慣。每一個幾點必須要有一個以<name>[@<unit-address>]形式的名字。
<name>是簡單的ascii字符串並且長度最大可以到31個字符。通常來說,節點是根據它所代表的設備類型來命名的。舉個例子,3com公司的以太網適配器節點可能會使用ethernet作爲它的名字,而不是3com509。
unit-address只有在節點描述含有地址的設備時會被包含進來。通常來說,unit address是用來訪問設備的基址,並且在節點的reg屬性中被羅列出來。我們稍後會在本文檔中介紹reg屬性。
兄弟節點必須被唯一地命名,不過對於不只一個節點的情況,我們通常會使用通用的名字,只要它們的地址不一樣就可以。(比如:seriali@101f1000 和serial@101f2000)。
如果需要更多節點命名方面的詳細情況,請參考ePAPR規範的第2.21部分
設備
系統中的每一個設備都由一個設備樹節點來代表。下一步就是用每一個設備對應的節點來填充樹。現在來說,新節點將會被置空直到我們可以討論地址範圍以及終端是怎樣安排的。
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
serial@101F0000 {
compatible = "arm,pl011";
};
serial@101F2000 {
compatible = "arm,pl011";
};
gpio@101F3000 {
compatible = "arm,pl061";
};
interrupt-controller@10140000 {
compatible = "arm,pl190";
};
spi@10115000 {
compatible = "arm,pl022";
};
external-bus {
ethernet@0,0 {
compatible = "smc,smc91c111";
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
rtc@58 {
compatible = "maxim,ds1338";
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
};
};
};
在這棵樹中,系統中的每個設備都被添加了響應的節點,並且層次結構反映了設備時怎樣連接到系統的。舉個例子來說,外部總線上的設備是外部總線的子節點,並且i2c設備是i2c總線控制器的子節點。通常來說,層次結構代表了從CPU角度看到的系統視圖。
在這裏這棵樹並不是合法的。它缺少了設備之間連接的信息。那部分數據會在稍後添加。
這棵樹中有一些需要注意的地方:
- 每一個設備節點都有一個compatible屬性
- flash節點的compatible屬性中有兩個字符串。閱讀下一部分來了解爲什麼會這樣。
- 之前提到過,節點的名字反映了設備的類型,而不是特定的型號。情況ePAPR規範的2.2.2部分,規範中提到了一系列已經定義好了隨處可能用到的通用節點名字
理解compatible屬性
設備樹中代表設備的每一個節點必須要有compatible屬性。compatible是操作系統用來決定哪個設備驅動綁定哪個設備的關鍵字。
compatible是字符串列表。列表中的第一個字符串以"<manufacturer>,<model>"的形式確定了節點代表的設備。接下來的字符串表示該設備可以兼容的其他設備。
舉個例子來說,Freescale MPC8349 片上系統(SoC)有一個串行設備實現了國家半導體 ns16550寄存器接口。MPC8349穿行設備的compatible屬性因此應該是:compatible = "fsl,mpc8349-uart","ns16550"。在這個例子中,fsl,mpc8349-uart確定了設備並且ns16550表示它在寄存器級別兼容國家半導體16550 UART。
注意:ns16550沒有廠商前綴純粹是因爲歷史原因(IBM-PC/AT吧..)。所有新的compatible值應該使用廠商前綴。
該操作允許現存的設備驅動綁定到更新的設備上,不過它仍然唯一地識別確切的硬件。
警告:不要在compatible值中使用通配符,比如"fsl,mpc83xx-uart"或者類似地。硅片廠商總是會做一些打破你通配符假設的變化,到那時再改變就爲時已晚了。相反,選擇一個特定的硅片實現並且使所有隨後的硅片與之兼容。
尋址是如何工作的
________________________________________________
可尋址的設備使用下面的屬性來將地址信息編碼進入設備樹:
- reg
- #address-cells
- #size-cells
每一個可尋址的設備獲取的reg,是以reg = <address1 length1 [address2 length2] [address3 length3] ...>形式的表格元組。每一個元組代表設備所使用的地址範圍。每一個地址值是被稱爲cells的一個或者多個32位整數的列表。相似地,長度值既可以是cells的列表,也可以是空的。
因爲地址和長度域都是長度可變的,所以父節點中的#address-cells和#size-cells屬性是用來說明每一個域中有多少個cells。換句話說,正確地翻譯一個reg屬性需要父節點的#address-cells和#size-cells值。爲了看到這一切是怎樣工作的,讓我們將尋址屬性添加到樣例設備樹中,首先從CPU開始
CPU尋址
討論到尋址時,CPU節點代表最簡單的情況。每一個CPU被賦予了一個唯一的ID並且CPU的ID沒有關聯的尺寸。
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
};
};
cpu節點中,#address-cells被設置爲1,#size-cells被設置爲0.這意味着子節點的reg值是代表地址且沒有size域的單個32位無符號整數。在這個例子中,2個cpu被賦予了地址0和1。cpu節點的#size-cells是0,因爲cpu僅僅被賦予了單個地址。
你能注意到reg值匹配節點名字中的值。按照慣例,如果一個節點具有reg屬性,這個節點必須包含單元地址,也就是reg屬性中的第一個地址值。
內存映射設備
不同於cpu節點中找到的單地址值,內存映射設備被分配了它會響應的一個地址範圍。#size-cells用來說明每一個子節點中的reg元組有多長。接下來的例子中,每一個地址值都是一個單元的(32位),每一個長度值也是一個單元,即典型的32位系統。64位系統可以把#address-cells和#size-cells賦值爲2從而在設備設備樹中得到64位尋址。
/ {
#address-cells = <1>;
#size-cells = <1>;
...
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
};
serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
};
gpio@101f3000 {
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
};
interrupt-controller@10140000 {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
};
spi@10115000 {
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
};
...
};
每一個設備都被分配了一個基地址,以及它被分配的區域的尺寸。本例中的GPIO設備地址被分配了兩個地址範圍:0x101f3000...0x101f3fff 和0x101f4000..0x101f400f。
有一些設備掛載在具有不同尋址策略的總線上。舉個例子來說,一個設備可以被連接到具有獨立片選信號的外部總線上。因爲每一個父節點定義了它子節點的尋址域,所以我們可以從最佳描述系統的角度來選擇地址映射方案。下面的代碼顯示了連接到外部總線上的設備的地址分配情況,並且這些外部總線具有編碼如地址的片選數字。
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";
reg = <1 0 0x1000>;
rtc@58 {
compatible = "maxim,ds1338";
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};
外部總線使用了2個單元的地址值。一個是片選數字,另一個是片選基地址的偏移。長度域仍舊是單個單元,因爲只有地址的偏移部分需要一個範圍。因此在本例中,每一個reg入口都包含了3個單元:片選數字,偏移以及長度。
因爲地址域被包含在節點以及它的子節點中,所以父節點可以自由定義任何總線上可行的尋址方案。直接父節點之外的節點通常不需要考慮本地尋址域,並且爲了從一個域到另一個域,地址必須被映射。
非內存映射設備
其他設備並沒有內存映射在處理器總線上。它們可以有尋址範圍,但是它們並不能直接被CPU訪問。相反,父節點設備的驅動會代表CPU間接地訪問。
現在來看i2c設備的例子,每一個設備被分配了一個地址,但是它沒有關聯的長度或者範圍。這看上去與CPU地址分配時一樣的。
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>;
};
};
範圍(地址轉換)
我們已經討論過了怎樣爲設備分配地址,但是這裏的這些地址只是本地的設備節點地址。它缺並沒有描述怎樣從那些地址映射到CPU可以使用的地址。
根節點總是描述從CPU的視角看到的地址空間。根節點的子節點已經使用了CPU的地址域,是、因此不需要任何顯示地映射。舉個例子來說,serial@101f0000設備是直接被分配了0x101f0000地址。
那些不是根節點的直接子節點的節點不能使用CPU的地址域。爲了得到一個內存映射的地址,設備樹必須制定如何從一個域地址轉換到另一個域。ranges屬性就是用於這個目的的。
這裏是添加了ranges屬性的設備樹例子。
/ {
compatible = "acme,coyotes-revenge";
#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
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>;
};
};
};
ranges是地址轉換清單。ranges表中的每一個條目是包含子節點地址,父節點地址以及子節點地址空間區域大小的元組。每一個域的大小分別由子節點的#address-cells值,父節點的#address-cells值以及子節點的#size-cells值決定。對於本例子中的外部總線,子節點地址是2個單元,父節點地址是1個單元,大小也是一個單元的。三個ranges是這樣被轉換的:
- 從片選0處偏移0開始的地方被映射到地址範圍0x10100000...0x1010ffff
- 從片選1處偏移0開始的地方被映射到地址範圍0x10160000...0x1016ffff
- 從片選2處偏移0開始的地方被映射到地址範圍0x30000000...0x10000000
或者,如果父節點和子節點的地址空間是相同的,那麼節點也可以添加空的ranges屬性。空的ranges屬性意味着子節點地址空間中的地址被1:1地映射到父節點地址空間。
你可能會問爲什麼地址轉換總是用在所有1:1映射的情況下。一些總線(比如PCI)具有完全不同的地址空間,而這些細節必須暴露給操作系統。其他總線具有DMA引擎,這些引擎需要知道總線上的實際滴孩子。有時候設備需要被組合在一起,因爲它們都共享有同樣的軟件可編程物理地址映射方法。該不該用1:1映射更大程度上取決於操作系統所需的信息以及硬件設計。
你應該也注意到,i2c@1,0節點中沒有ranges屬性,原因是不像外部總線,i2c總線上的設備沒有內存映射到CPU的地址域。相反地,CPU通過i2c@1,0設備間接地訪問rtc@58設備。缺少ranges屬性意味着,設備不能直接被除了其父節點之外的任何設備訪問
中斷是怎樣工作的
________________________________________________
中斷不同於遵循樹自然結構的地址範圍轉換,中斷信號可能來自以及終止在機器的任何設備。不像設備樹中自然表示的設備尋址,中斷信號是以獨立於設備樹的節點之間的鏈接表示的。四個屬性用來描述中斷的聯繫:
- interrupt-controller —— 一個空的屬性聲明接收中斷信號的設備爲節點
- #interrupt-cells —— 這是中斷控制器節點的屬性。它表明這個中斷控制器的中斷描述符符中有多少單元。(類似於#adderss-cells以及#size-cells)
- interrupt-parent —— 包含phandle的設備節點的一個屬性,這個phandle指向它所連接到的中斷控制器
- interrupts —— 包含中斷描述符列表的設備節點的一個屬性,每一個設備上的中斷輸出信號都有一個
中斷描述符是一個或多個單元的數據(由#interrupt-cells指定),它們指定設備連接到哪個中斷輸入。大部分設備只有單箇中斷輸出,如下面的例子所示,不過設備上也有可能有多箇中斷輸出。中斷描述符的意義完全取決於中斷控制器設備的綁定。每一箇中斷控制器可以決定需要多少個單元才能唯一地定義一箇中斷輸入。
下面的代碼添加了連接到我們的Coyote's Revenge樣例機器的中斷。
/ {
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>;
interrupts = < 6 2 >;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
interrupts = < 7 3 >;
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};
};
有一些事情需要注意:
- 這個機器只有一箇中斷控制器,interrupt-controller@10140000
- 標籤'intc:'已經被添加到中斷控制器節點,並且標籤被用來分配指向根節點的interrupt-parent屬性的phandle。這個interrupt-parent值變成了系統的默認值,因爲所有的子節點都繼承了它,除非它被顯示地覆蓋。
- 每一個設備都使用一個interrupt屬性來制定一個不同的中斷輸入信號。
- #interrupt-cells是2,因此每一箇中斷描述符有2個單元。這個例子採用了使用了常見的模式,用第一個單元來編碼中斷線號,用第二個單元來編碼標誌,比如高有效還是低有效又或者是邊緣觸發還是電平觸發。對於任何給定的中斷控制器,請參考控制器綁定文檔來了解描述符是怎樣編碼的。
設備特定數據
除了公共的屬性之外,我們可以添加任何屬性以及子節點到節點。我們可以添加操作系統需要的任何數據,只要遵守一些規則即可。
首先,新的設備特定的屬性名字應該使用生產廠商的前綴從而它們不會與現存的標準屬性名稱衝突。
其次,屬性以及子節點的意義必須以綁定的形式記錄下來從而設備驅動作者瞭解怎樣翻譯數據。綁定記錄一個特定的compatible值意味着什麼,它應該要有什麼屬性,它可能會有什麼樣的子節點以及設備表示什麼。每一個唯一的compatible值應該有它自己的綁定(或者聲明與其他compatible值得兼容性)。新設備的綁定在這個wiki中被記錄。請看主頁描述文檔格和審查過程。
特殊節點
________________________________________________
aliases節點
一個特定的節點通常是以完整的路徑來引用,比如/external-bus/ethernet@0,0,不過當一個用戶真的想知道“哪個設備是eth0”時,這將會很繁瑣。aliases節點可以用來爲一個完整的設備路徑分配一個短的別名。比如:
aliases {
ethernet0 = ð0;
serial0 = &serial0;
};
當需要爲設備指定一個標示符時,操作系統歡迎大家使用別名。
你將會注意到這裏使用了一個新的語法。propert = &label; 語法分配由標籤引用並作爲字符串屬性的的完整節點路徑。這與早前使用的phandle = <&label>;不同,它在單元中嵌入了phandle值。
chosen節點
chosen節點並不代表真實的設備,不過充當爲在固件和操作系統之間傳遞數據的地方,比如啓動參數。在chosen節點中的數據不代表硬件。典型情況下,chosen節點在.dts源文件中留空並在啓動時填充。
在我們的系統中,固件可能會添加下面的內容到chosen節點:
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
高級主題
________________________________________________
高級的樣例機器
現在我們已經理解了基本定義,讓我們添加一些硬件到樣例機器中來討論一些更復雜的使用案例。
高級的樣例機器添加了一個PCI主橋配有內存映射到0x10180000的控制寄存器和編程至從0x80000000地址以上開始的BARs。
假設我們已經知道關於設備樹以上內容,我們可以從添加如下節點描述PCI主橋開始。
pci@10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
};