英語原文地址: htttp://devicetree.org/Device_Tree_Usage 本文介紹如何為新的機器或板卡編寫設備樹(Device Tree), 它旨在概要性的介紹設備樹概念,以及如何使用它們來描述機器或者板卡。 有關設備樹數據格式的完整技術描述,請參閱ePAPR v1.1規範。 ...
英語原文地址: htttp://devicetree.org/Device_Tree_Usage 本文介紹如何為新的機器或板卡編寫設備樹(Device Tree), 它旨在概要性的介紹設備樹概念,以及如何使用它們來描述機器或者板卡。 有關設備樹數據格式的完整技術描述,請參閱ePAPR v1.1規範。 ePAPR技術規範比本文所介紹的基礎主題更加詳細,所以請參閱它瞭解本頁未涉及的更高級用法。 ePAPR目前正在用Devicetree規範文檔的新名稱進行更新。
1 基礎數據結構
設備樹(Device Tree)是一種包含節點和屬性的簡單樹形結構。屬性是鍵值對,節點則可能包含屬性和子節點。 例如,下麵是一個.dts格式的簡單設備樹:/dts-v1/; / { node1 { a-string-property = "A string"; a-string-list-property = "first string", "second string"; // hex is implied in byte arrays. no '0x' prefix is required a-byte-data-property = [01 23 34 56]; 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 { }; }; };
上面這個設備樹,顯然沒有實際用處,因為它沒有描述任何信息,但是它確實顯示了節點的結構和屬性:
- 一個簡單的root節點:“/”
- 一組子節點:“node1”和“node2”
- 一組node1的子節點:“child-node1”和“child-node2”
- 一堆分散在設備樹中的屬性
- 文本字元串(以null結尾),用雙引號表示:
- string-property = "a string";
- ‘cell’是被<>括弧括起來的32bit無符號int數
- cell-property = <0xbeef 123 0xabcd1234>;
- 二進位數據是被[]括弧括起來
- binary-property = [0x01 0x23 0x45 0x67];
- 不同類型的數據,可以以逗號“,”串起來
- mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
- 逗號“,”也可以用來表示字元串列表:
- string-list = "red fish", "blue fish";
2 基礎概念Basic Concepts
為了理解設備樹(device tree)如何使用,我們將從一個簡單的設備(machine)開始,建立一個設備樹(device tree),然後一步一步描述它。2.1示例設備(sample machine)
假設有這樣一臺虛擬的設備(基於ARM的通用版本),由“Acme”公司生產,名為“Cpyote's Revenge”:- 一個32位寬的ARM CPU
- 處理器本地匯流排連接到記憶體映射串口、spi匯流排控制器、i2c控制器、中斷控制器和外部匯流排橋
- 從0地址開始的256MB 位元組的SDRAM
- 2個串口,寄存器基地址分別是0x101F1000和0x101F2000
- GPIO的控制寄存器的基地址是0x101F3000
- SPI的控制寄存器的基地址是0x10170000,並掛載下列設備
- MMC slot,SS管腳連接到GPIO1
- 外部匯流排橋接著下列設備
- SMC SMC91111網路設備連接到外部匯流排,基地址是0x10100000
- i2c 控制寄存器基地址是0x10160000,並掛載下列設備
- Maxim DS1338實時時鐘,其地址是1101000(0x58)
- 64M的Nor flash基地址是0x30000000
2.2 初始化結構體(Initial structure)
第一步是為設備鋪設骨架, 這是有效設備樹所需的最小結構。在這一階段,你需要能唯一的標識設備。/dts-v1/; / { compatible = "acme,coyotes-revenge"; };“compatible”表明系統的名字。它包含一個以“製造商”,“品牌”形式組成的字元串。準確的表明設備非常重要,而且需要包含製造商的名稱以避免命名衝突。 由於操作系統將使用compatible值來決定如何在機器上運行,因此將正確的數據寫入此屬性中非常重要。理論上,一個操作系統唯一識別一臺設備只要有“compatible”屬性就夠了。如果所有設備細節都是硬編碼的,那麼操作系統可以專門在Device Tree的最頂層的“compatible”屬性中查找 "acme,coyotes-revenge"即可。
2.3 CPUs
下一步是描述每一個CPU。添加一個名為“cpus”的容器節點,為每一個CPU創建一個子節點。在當前的例子里,該系統是一個源自ARM的雙核Cortex A9系統。/dts-v1/; / { compatible = "acme,coyotes-revenge"; cpus { cpu@0 { compatible = "arm,cortex-a9"; }; cpu@1 { compatible = "arm,cortex-a9"; }; }; };每個CPU節點的“compatible”的屬性都是一個字元串,以“製造商”,“型號”的形式表明CPU的準確型號,就像設備樹(DT)最頂層的“compatible”屬性一樣。 稍後將向cpu節點添加更多屬性,但是我們首先需要討論更多的基本概念。
2.4 節點名字(Node Names)
首先,我們需要瞭解命名的規則。每一個節點必須有一個名字,名字的形式必須是“ <name>[@<unit-address>”。<name>是一個簡單的ascii字元串,最大長度為31位元組。通常,節點是根據它所代表的設備來命名。比如,一個3com公司的網路適配器的名字可能會是“ethernet”,而不是“3com509”。如果這個節點描述設備需要一個地址,則包含“<unit-address>”欄位。通常,這個地址是訪問該設備寄存器所需要的首地址。而且會列在node的“reg”屬性里。關於“reg”屬性將在本文後續的內容中介紹。兄弟節點的名字不能相同,但是通常都是<name>欄位相同,而<unit-address>欄位不同( (比如, serial@101f1000 & serial@101f2000).)。可以查閱ePAPR的2.2.1節來瞭解節點命名的全部細節。2.5 設備(Devices)
系統中的每個設備都由一個設備樹節點表示。下一步是用每個設備的對應的節點填充樹。現在,新的節點將保持為空,直到我們可以討論如何處理地址範圍和irq。/dts-v1/; / { 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節,其中列出了已定義的通用節點名。應該儘可能使用這些節點名,而不要發明新的名字。
2.6 理解“compatible”屬性
設備樹中每一個表示設備的節點都要有“compatible”屬性。 “compatible”屬性是操作系統用來決定將哪個設備驅動程式綁定到這個設備的關鍵。 “compatible”是一個字元串列表,列表中的第一個字元串以“製造商”,“型號”的形式指定確切的設備。接下來的字元串表示該設備相容的其他設備。例如,Freescale MPC8349片上系統(Soc)有一個串列設備,執行National Semiconductor的 ns16550 寄存器介面。那麼Freescale MPC8349的串列設備的“compatible”屬性就是“ fsl,mpc8349-uart”,“ns16550”.在這個例子里,“ fsl,mpc8349-uart”表示確切的設備,“ns16550”表示它在寄存器級別與National Semiconductor的ns16550串列設備相容。這裡的“ns16550”沒有製造商的名字,是由於歷史原因。所有新的相容性設備名稱,都需要加製造商的名字。這種做法允許將現有設備驅動程式綁定到新設備,同時仍然惟一地標識確切的硬體。 警告:不要使用通配符來實現相容性,比如 "fsl,mpc83xx-uart"或者類似的值。但晶元供應商總是會修改命名規則,一旦等到其改變打破了你的通配符假設,再來修改就已經晚了。所以,請選擇一個具體的晶元型號,然後再相容性列表裡面列出所相容的晶元型號。3 如何定址(How addressing work)
可定址設備使用以下屬性將地址信息編碼到設備樹中:* reg * #address-cells * #size-cells每一個可定址設備都有一個叫“reg”的屬性,reg由一系列元組構成,形式是reg = <address1 length1 [address2 length2] [address3 length3] ... >。也就是address、length交替出現。每一個元組代表一個設備使用的地址範圍。每一個地址的值是一個或者多個32位int型數構成的列表,稱為cells。長度的取值也是一個cells列表,或者為空。 由於address和length欄位都是可變大小的變數,因此父節點中的#address-cells和#size-cells屬性用於說明每個欄位中有多少個單元格。換句話說,正確地解釋reg屬性需要父節點的#address-cells和#size-cells值。要瞭解這一切是如何工作的,讓我們將定址屬性添加到示例設備樹中,從cpu開始。#address-cells表示address的長度,#size-cells表示length的長度,如果為0,則表示沒有。
3.1 CPU定址
當講述如何定址的時候,CPU節點是一個最簡單的例子。每個CPU被分配一個唯一的ID,而且這個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>; }; };在cpu節點中,將#address-cells設置為1,將#size-cells設置為0。這意味著子reg值是一個uint32數,它是一個沒有size欄位的地址。在這種情況下,為這兩個cpu分配了地址0和1。對於cpu節點,#size-cells為0,因為每個cpu只分配一個地址,沒有其他的地址。 您還會註意到reg值與節點名中的@符號後面的值相同。按照習慣,如果節點具有reg屬性,那麼節點名必須包含單元地址,而且是reg屬性中的第一個地址的值。
3.2 記憶體映射設備
與CPU節點只有一個地址值不同,記憶體映射設備會分配一個它需要響應的地址區間。#size-cells表示reg中length的位寬,如果是32位寬就是1,64位寬就是2.在下麵的示例里,每個address值是1個單元格(32位),每個length也是一個單元格(32位),這在32位操作系統中是非常常見的。在64位系統可能將#address-cells、#size-cells都設置為2,從而完成64位地址空間的定址。/dts-v1/; / { #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 and 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>; }; };匯流排“ external-bus”使用2個單元格來表示地址域,一個是片選號,一個是該片選的設備的基地址偏移。“length”欄位仍然是一個單元格,因為只有地址的偏移需要一個範圍。所以在這個例子里,每一個reg包含3個單元格:片選,地址偏移,偏移的範圍。 由於地址域包含在節點及其子節點中, 所以父節點可以自由地定義任何對匯流排有意義的定址方案。設備節點不需要考慮本節點之外的地址域的情況。地址映射必須按照地址域一個一個的進行。
3.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>; }; };
3.4 ranges(地址轉換)
我們已經討論瞭如何為設備分配地址,但目前這些地址只在設備節點地址域有意義。它還沒有描述如何將這些地址映射到CPU可以使用的地址。 根節點總是以CPU的視角來描述地址空間。根節點的子節點已經使用了CPU的地址域,因此不需要任何顯式映射。例如,serial@101f0000設備被直接分配地址0x101f0000。 一些非根節點的子節點的設備沒有直接使用CPU地址域。 為了獲得記憶體映射地址,設備樹必須指定如何將地址從一個域轉換到另一個域。ranges屬性正是為這一目的而設計的。 下麵就是一個簡單的例子,展示了一個包含ranges屬性的設備樹。/dts-v1/; / { 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個單元格,子地址大小也是1個單元格。因此,三項ranges翻譯如下:
* Offset 0 from chip select 0 is mapped to address range 0x10100000..0x1010ffff * Offset 0 from chip select 1 is mapped to address range 0x10160000..0x1016ffff * Offset 0 from chip select 2 is mapped to address range 0x30000000..0x30ffffff或者,如果父地址空間和子地址空間相同,則節點可以添加一個空的“ranges”屬性。空“ranges”屬性的存在意味著子地址空間中的地址將1:1映射到父地址空間。 您可能會問,為什麼要使用地址轉換,而所有這些都可以用1:1映射來編寫。 有些匯流排(如PCI)具有完全不同的地址空間,其詳細信息需要暴露給操作系統。其他的DMA引擎需要知道匯流排上的真實地址。有時需要將設備分組,因為它們共用相同的軟體可編程物理地址映射。是否應該使用1:1映射在很大程度上取決於操作系統和具體的硬體設計信息。 您還應該註意到,i2c@1,0節點中沒有ranges屬性。原因在於,與外部匯流排不同,i2c匯流排上的設備不是映射到CPU地址域中的記憶體。相反,CPU通過i2c@1,0設備間接訪問rtc@58設備。缺少範圍屬性意味著設備不能被除其父設備之外的任何設備直接訪問。