? ? ? ? 本文通過為一個新machine寫一個設備樹來介紹設備樹相關(guān)的概念,以及如何來描述一個machine。
關(guān)于設備樹的技術(shù)細節(jié)描述,需要參考ePAPR文檔,ePAPR文檔中包含了大量的基礎(chǔ)語法之外的細節(jié),如果你需要了解更多本文之外的設備樹細節(jié),請參考ePAPR文檔。
基本數(shù)據(jù)格式
設備樹是一個由節(jié)點及屬性組成的簡單樹結(jié)構(gòu)。屬性是基于key-value對的,節(jié)點則可以包含子節(jié)點以及屬性。
如,下面這個樹就是一個典型結(jié)構(gòu):
/ {
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 {
};
};
};
這顆樹顯然沒什么實際用途,因為它沒有描述任何有意義的內(nèi)容,但是,我們通過它可以了解什么是屬性,什么是節(jié)點。這顆樹可以解讀如下:
一個根節(jié)點: "/"
兩個子節(jié)點: "node1" 和 "node2"
node1下面又有兩個子節(jié)點:"child-node1" 和 "child-node2"
一堆的屬性分散與整顆樹的各個節(jié)點上
譯者注:可以這么簡單理解:節(jié)點就是樹枝,屬性就是樹葉;樹枝上可以有再長樹枝也可以長樹葉,而樹葉上則不會再長樹枝。
屬性是基于key-value結(jié)構(gòu)的,value可以為空或者特定格式的字符串內(nèi)容。由于數(shù)據(jù)類型并不被編碼到最終的數(shù)據(jù)結(jié)構(gòu)中,設備樹源代碼中僅能支持有限的幾種基本數(shù)據(jù)類型。
text string(以null結(jié)束),以雙引號括起來,如:
string-property = "a string"
cells?是32位無符號整形數(shù),以尖括號括起來,如
cell-property = <0xbeef 123 0xabcd1234>
binary data?以方括號括起來,如:
binary-property = [0x01 0x23 0x45 0x67];
不同類型數(shù)據(jù)可以在同一個屬性中存在,以逗號分格,如:
mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
多個字符串組成的列表也使用逗號分格,如:
string-list = "red fish","blue fish";
基本概念
為了搞清楚設備樹該如何使用,我們將一步一步的為一個Sample Machine建立一顆設備樹。
Sample Machine
姑且想象有這么一臺機器,它由"Acme"出產(chǎn),名為"Coyote's Revenge", 配置如下:
處理器的本地內(nèi)存總線連接如下設備:串口,SPI總線控制器,I2C控制器,終端控制器以及外部總線橋
兩個串口,位于地址:0x101F1000 及 0x101F2000
GPIO控制器位于地址:0x101F3000
SPI控制器位于地址:0x10170000,在它下面連接了如下設備:
MMC卡槽,其SS腳連接了GPIO1
外部總線橋上接了如下設備:
SMC91111 以太網(wǎng)控制器,位于地址:0x10100000
I2C控制器,位于地址:0x10160000,在它下面連接了如下設備:
64MB NOR flash 位于地址:0x30000000
初始結(jié)構(gòu)
第一步是要建立一個基本結(jié)構(gòu)來使得這顆設備樹能描述對應的Machine
/ {
compatible = "acme,coyotes-revenge";
};
compatible這個屬性用于執(zhí)行系統(tǒng)名,通常它是以?"廠商,型號"?這樣的字符串形式存在。它能準確的描述對應的設備的特征。
CPU描述
下一部就是描述每一個CPU了。在cpus節(jié)點中,每個CPU就是一個子節(jié)點。這種情況在多核的ARM Cortex A9系統(tǒng)中很常見。
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
};
每個cpu的子節(jié)點中有個compatible屬性,它描述了CPU的具體型號,形式通常也是?"廠商,型號"
當然,CPU的細節(jié)不是這么一個compatible屬性能描述清楚的,后面我們會再逐步加入。
節(jié)點名
有必要為節(jié)點的命名講幾句。每個節(jié)點的名字都應該是這樣的形式:?[@]
尖括號是必須的,方括號是可選的。
是一個ASCII字符串,長度不超過31個字符。通常,節(jié)點名都是與設備的類型有關(guān)聯(lián)的。比如,3com的以太網(wǎng)卡,通常節(jié)點名就叫?ethernet?而不叫?3com509
被作為節(jié)點名的一部分時,用來描述設備的地址。通常,unit-address就是設備的寄存器地址,這個地址是被列舉在節(jié)點的reg屬性中的。在后文中我們會介紹reg屬性的內(nèi)容。
同級別的兄弟節(jié)點的節(jié)點名必須唯一(不可重名),但如果name一致而address不一致則是正常情況(比如,serial@101f1000 與 serial@101f2000)。
關(guān)于節(jié)點的命名規(guī)則細節(jié),請參考ePAPR文檔的2.2.1節(jié)。
設備
系統(tǒng)中的每個設備都對應著設備樹中的一個節(jié)點。好了,下一步我們就是為每個設備都加上對應的節(jié)點。
目前,我們僅為每個設備增加一個空節(jié)點,待后面介紹了中斷號及地址范圍的概念后再行補充。
/ {
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";
};
};
};
上面這棵樹中,系統(tǒng)中的每個設備都被添加了一個節(jié)點,而且節(jié)點的結(jié)構(gòu)真實反應了設備是如何掛載在系統(tǒng)上的。比如,ethernet,i2c等節(jié)點是external-bus的子節(jié)點,rtc設備是i2c總線下的子節(jié)點。通常,設備樹的結(jié)構(gòu)都是以CPU的視角來反應出來的。
上面這棵樹還有幾點不足,它缺少了設備的關(guān)鍵信息。這些信息將在后文中逐步添加上去。
關(guān)于上面這顆樹,我們還需要注意:
每個設備節(jié)點都都一個compatible屬性
flash這個節(jié)點的compatible屬性有兩個字串,下面一節(jié)將介紹為什么這么寫。
在前文中曾提到:節(jié)點名反應的是設備類型而非設備型號。請參考ePAPR的2.2.2節(jié)中列出的常用節(jié)點名。
理解compatible屬性
每個設備節(jié)點都需要一個compatible屬性。compatible屬性是系統(tǒng)賴以查找對應的設備驅(qū)動程序的一個關(guān)鍵值,系統(tǒng)就是根據(jù)它的值來查找這個設備應該使用哪一個驅(qū)動的。
compatible屬性的值是一個字串表。第一個字串以"廠商,型號"的形式描述了準確的設備信息。后面一個字串則表示與它兼容的其他設備。
比如,F(xiàn)reescale MPC8349有一個串口設備是由National Semiconductor ns16550寄存器接口來實現(xiàn)的。所以對MPC8349的串口設備,它的compatible屬性我們就可以這樣寫:compatible = "fsl,mpc8349-uart", "ns16550",對于這樣的情況,fsl,mpc8349-uart準確描述了這個設備,而ns16550則說明了這個設備是與National Semiconductor ns16550寄存器接口兼容的。
注:ns16550它沒有廠商名這個信息,這是由于歷史原因。但所有新創(chuàng)建的compatible屬性都應該有廠商名這個前綴。
compatible屬性的這一特性,使得我們可以讓新設備使用系統(tǒng)中已有的舊驅(qū)動。
警告:不要在compatible屬性中使用通配符,如 "fsl,mpc83xx-uart" 或類似的。因為半導體廠商會不定期的更新他們的設計這會破壞你的通配符規(guī)則。選擇具有更好兼容性的方案才是正途。
地址是怎么工作的
可尋址設備是通過使用以下屬性來將地址信息編碼到設備樹中的:
reg
#address-cells
#size-cells
可尋址設備通過reg屬性來獲取寄存器相關(guān)的地址信息列表,reg屬性的形式如下:
reg =
每一組address length對應了設備所使用的一個地址區(qū)域。
address是一個list,其中包含一個或多個32位整數(shù),我們把它叫做 cells。同理,length也是一個list,可以是多個cell或為空。
由于address和length的長度都是不固定的,所以有了#address-cells和#size-cells這兩個屬性。這兩個屬性被放到父節(jié)點中用于描述每個區(qū)域有幾個cell。簡單的說,就是reg屬性需要配合父節(jié)點的#address-cells和#size-cells來配合使用。為了弄明白它們是怎么工作的,下面我們就來為這個設備加上地址相關(guān)的屬性,先從CPU開始。
CPU地址
CPU節(jié)點的地址用法是最簡單的。每個CPU被分配了唯一的ID號,而且這個沒有size。
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
};
};
在上面的cpus節(jié)點中,#address-cells被設為了1,#size-cells則被設為了0,這就表示它的子節(jié)點中的reg屬性是一個32位整數(shù)的address,而且沒有size部分。
在上面的例子中,你可能注意到了節(jié)點名中的寄存器地址與reg數(shù)值一樣。
約定俗成的做法是,如果一個節(jié)點有reg屬性,則節(jié)點名中也需要包含unit-address這個部分,而unit-address的數(shù)值則是reg屬性的第一個address值。
內(nèi)存映射設備
與cpu節(jié)點中的這種單地址不同,內(nèi)存映射設備所分配的是一個地址范圍,而這個地址范圍則是由#size-cells和節(jié)點中的reg屬性的size區(qū)域來決定的。下面這個例子中,每個address是1個cell(32bit),且每個長度值也是一個cell。在32位系統(tǒng)中#size-cells通常就是這樣設置為1的。而早64位系統(tǒng)中,#address-cells和#size-cells則通常設置為2。
/ {
#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 >;
};
...
};
每個設備被分配了一個基地址以及一個size。上面的例子中,gpio設備被分配了兩個地址段: 0x101f3000~0x1013fff 以及 0x101f4000~0x101f4fff。
有些設備在系統(tǒng)總線上的地址不連續(xù)。比如,一個設備可能通過不連續(xù)的片選線連接在外部總線上。
通過在父節(jié)點設置合適的#address-cells和#size-cells,地址映射機制可以準確的描述內(nèi)存映射關(guān)系。下面的代碼中展示了一個不同片選信息在是如何使用的。
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>;
};
};
上面的例子中,external-bus使用了兩個cell來描述address;一個表示片選號,另一個表示與片選基地址間的偏移量。length區(qū)域則用了一個cell來描述。在這個例子中,每個reg節(jié)點包含3個cell,分別是:片選號,偏移量,長度
非內(nèi)存映射設備
還有一些設備在總線上并不是不是內(nèi)存映射型的。他們可以有地址空間,但他們沒有通過CPU直接訪問地址空間的能力。取而代之的是他們的父設備驅(qū)動擁有間接訪問內(nèi)存空間的能力。
以I2C設備(不是I2C總線哦)為例子,每個設備被分配了一個地址,沒有l(wèi)ength這個字段??雌饋韺嶋H上與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>;
};
};
Ranges(地址變換)
上文已經(jīng)討論過了我們?nèi)绾畏峙涞刂方o設備,但是這個所謂的地址僅僅是在設備節(jié)點中的本地地址。它無法描述該如何將這些本地的地址映射到CPU能訪問的地址空間中。
根節(jié)點中一定有描述CPU的地址空間。子節(jié)點所用的地址空間就來自與CPU的地址空間,所以不需要進行額外的地址映射。
如:serial@101f0000 就是直接分配的地址0x101f0000。
如果一個節(jié)點它不是跟節(jié)點下的子節(jié)點,那么它就不能用CPU的地址空間。為了能將一個地址空間的地址映射到另一個地址空間,range屬性被創(chuàng)造出來了。
下面是在一個簡單的設備樹中增加range屬性。
/ {
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屬性就定義了一個地址轉(zhuǎn)換規(guī)格。在這個表中的每個節(jié)點表示一個地址轉(zhuǎn)換關(guān)系。
ranges屬性中每個字段的大小取決于當前節(jié)點的#address-cells,父節(jié)點的#address-cells以及當前節(jié)點的#size-cells。
比如上面的例子中,external-bus節(jié)點的地址長度是2,它的父節(jié)點的地址長度是1,size長度是1。所以ranges中的三個地址規(guī)則可以這樣解讀:
CS0,偏移量為0的本地地址被映射到父節(jié)點地址空間的 0x10100000~0x1010ffff
CS1,偏移量為1的本地地址被映射到父節(jié)點地址空間的 0x10160000~0x1016ffff
CS2,偏移量為0的本地地址被映射到父節(jié)點地址空間的 0x30000000~0x31000000
更方便的是,如果父節(jié)點與子節(jié)點的地址空間完全匹配,則子節(jié)點可以只定義一個空的ranges屬性。
空ranges屬性所表示的意思就是子節(jié)點的地址空間與父節(jié)點地址空間是1:1的映射關(guān)系。
你可能會問:為什么上面會寫1:1的映射關(guān)系?有些總線結(jié)構(gòu)(如PCI總線)擁有自己獨立的地址空間,但需要向操作系統(tǒng)開放。還有些設備有DMA引擎,這就需要知道總線上的實際地址。還有些時候幾個設備因為使用同樣的物理地址空間而需要組合在一起。在硬件設計以及操作系統(tǒng)的大量特性上決定了地址映射是不是1:1的映射關(guān)系。
也可能還注意到了上文中的i2c@1,0節(jié)點中沒有ranges屬性。原因是I2C總線不像外部總線,它的地址空間并沒有映射到CPU的地址空間中去。實際上,CPU對rtc@58的訪問是通過i2c@1,0這個設備來間接達成的。沒有ranges屬性正表明了這個設備是不能直接訪問除父節(jié)點外的任何設備的特性。
中斷是怎樣工作的
不想地址空間映射表那樣是遵循設備樹的自然結(jié)構(gòu)的(父傳子),中斷信號可以被machine中的任何設備產(chǎn)生或終止。終端信號獨立與設備樹將各個節(jié)點關(guān)聯(lián)起來。描述一個中斷需要4個屬性:
interrupt-controller 這個屬性沒有值,他表示這個節(jié)點是一個接收中斷信號的設備
#interrupt-cells 這個屬性是一個interrupt-controller節(jié)點的屬性,他說明了這個 interrupt-controller的每個中斷說明符(interrupt specifier)有幾個cells,類似#address-cells 與 #size-cells的作用
interrupt-parent 這是一個設備節(jié)點的屬性,用于表明當前設備的中斷是屬于哪一個interrupt-controller的,如果沒有這個屬性,則繼承其父節(jié)點的interrupt-parent屬性
interrupts 這是一個設備節(jié)點的屬性,他是?中斷說明符?列表,每一個?中斷說明符?表示此設備的一個中斷信號輸出。
中斷說明符是由一個或多個cell數(shù)據(jù)(#interrupt-cells?)來描述一個設備是與哪一個終端信號輸入設備相連接的。多數(shù)設備都只有一個中斷信號輸出,如下面的例子,但也有可能存在一個設備有多個終端信號輸出的情況。中斷說明符的含義與具體的終端控制器(?interrupt-controller)有關(guān)。每個終端控制器都可以決定它的輸入信號的中斷說明符有幾個cell數(shù)據(jù)。
下面的代碼中為我們的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>;
};
};
};
有些細節(jié)需要各位注意:
此machine僅有一個中斷控制器:interrupt-controller@10140000
標簽"intc:"被加到了中斷控制器的節(jié)點上,這個標簽在父節(jié)點上創(chuàng)建了一個phandle,這個phandle就是父節(jié)點的interrupt-parent。所以這個中斷控制器就成了系統(tǒng)所有子節(jié)點的默認終端控制器,只有當子節(jié)點明確的聲明了其interrupt-parent才會被覆蓋。
每個設備使用interrupt屬性來區(qū)分不同的終端輸入線。
#interrupt-cells(在interrupt-controller@10140000節(jié)點中)的值是2,所以,每個中斷說明符由兩個cell數(shù)據(jù)組成。這個例子中用的是最常見的?中斷說明符?形式,第一個cell表示中斷線的序號,第二個cell表示終端類型的flag(表示高有效,低有效。。。),對于不同的終端控制器,需要閱讀對應的binding document來得知其?中斷說明符?的格式。
設備特定數(shù)據(jù)
在常用的屬性之外,我們還能為一個節(jié)點自行添加屬性及子節(jié)點。只要是系統(tǒng)需要的任何數(shù)據(jù)都能被我們按特定的規(guī)則來添加。
首先,新設備特定的屬性名需要使用?廠商前綴,這樣能避免與系統(tǒng)已有的標準屬性名稱沖突。
第二,每個屬性都應該在某個binding文檔中有相關(guān)的說明,這樣驅(qū)動作者才能知道如何使用這些屬性數(shù)據(jù)。
第三,將新的binding資料在devicetree-discuss@lists.ozlabs.org中post出來,大家對binding的代碼審查將能規(guī)避大部分常識性錯誤。
特殊節(jié)點
aliases 節(jié)點
這個特殊節(jié)點用來引用一個長路徑,比如/external-bus/ethernet@0,0,它是長路徑的縮寫或叫別名。比如:
aliases {
ethernet0 = e0;
serial0 = &serial0;
};
使用別名來標識一個設備是備受操作系統(tǒng)歡迎的做法。
chosen節(jié)點
chosen節(jié)點并不代表一個真正的設備,而是用來在Firmware與操作系統(tǒng)間傳遞數(shù)據(jù),如啟動參數(shù)。
通常chosen節(jié)點在dts中被置空。
在我們這個例子中,被添加了如下的啟動參數(shù):
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
?
評論
查看更多