device tree ( external-bus , rangs , eg : 32位arm cpu dts文件由簡單到復雜例子 ------ 很好 , 重點 )


(1).另外一個例子

http://46aae4d1e2371e4aa769798941cef698.devproxy.yunshipei.com/woshidahuaidan2011/article/details/52948732

 

(2)

轉載於 : https://github.com/huaqianlee/blog-file/blob/master/_posts/Android/%E9%AB%98%E9%80%9A%E5%B9%B3%E5%8F%B0Android%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8BLinux%E5%86%85%E6%A0%B8%E8%AE%BE%E5%A4%87%E6%A0%91-DT-Device-Tree-dts%E6%96%87%E4%BB%B6.md

 

 

title: "高通平台Android源碼分析之Linux內核設備樹(DT - Device Tree)" date: 2015-08-19 22:11:46 categories: Android

tags: [源碼分析,Qualcomm,kernel,譯文,dts文件]

剛開始接觸Android源碼的時候,發現在kernel里面多了一種dts文件,因為當初自學Linux時和在第一家公司做物聯網模型時都是用的比較老的內核,內核代碼還比較混亂,沒有采用dts這種方便簡潔的格式。后面才知道這是因為Linus的一句”this whole arm thing is a fucking pain in ass“促進改革的,記得Linux早期代碼里面板級細節都是在C文件中描述的,代碼就顯得十分臃腫和混亂。如此優化之后就顯得簡潔多了,並且也更易於學習、移植。   今天准備專門來分析一下內核設備樹,主要按照如下三個方向來分析:

  • Device Tree組成及用法;
  • DTS文件解析常用api介紹;
  • DTS文件的編譯;
  • 高通Android源碼中dts文件引用流程;

Device Tree組成及用法

Device Tree由一系列node(節點)和property(屬性)組成,節點本身可包含更多的子節點。屬性是成對出現的name-value鍵值對。在device tree中主要描述如下信息:

  • CPU的數量及類別
  • 內存基地址和size
  • 總線和橋
  • 外設連接
  • 中斷
  • GPIO
  • CLOCK

Device Tree在內核的作用有點類似於描述出PCB上的CPU、內存、總線、設備及IRQ GPIO等組成的tree結構。然后經由bootloader傳遞給內核,內核再根據此設備樹解析出需要的i2c、spi等設備,然后將內存、IRQ、GPIO等資源綁定到相應的設備。

lk中通過tag傳遞到kernel,文件路徑:bootable/bootloader/lk/app/aboot/aboot.c,由DEVICE_TREE宏開關控制

DTS(device tree source)

dts文件是一種ASCII文本格式的device tree描述文件,其結構明了,第一次看到都能大概猜出其描述意圖。在內核中arm部分,基本上一個.dts文件對應一個arm的machine,一般位於kernel/arch/arm/boot/dts。由於一個soc可能對應多個machine, 所以一般講多個machine通用的部分提煉為一個.dsti文件,有點類似於頭文件的作用,引用方式也類似:#include "xxx.dtsi",dtsi文件也可以相互引用。

dts中的基本元素

dts中的基本元素為節點和屬性,節點可以包含屬性和子節點,屬性為name-value鍵值對,如下:

/ { 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; // 值為kog /* each number (cell) is a uint32 */ a-cell-property = <1 2 3 4>; // cells(由uint32組成) child-node1 { }; }; }; 

上述dt並沒有什么真實用途,沒有描述任何東西。不過展示了dt的結構:

  • 一個根節點:"/";
  • 一對子節點:"node1"和"node2";
  • 子節點的子節點:"child-node";
  • 屬性定義: 屬性值可以為空、字符串、cells(整數組成)、數組及二進制等任意字節流;

屬性中常用的字節流如下:

# 字符串,用雙引號引用: string-property = "A string"; #cells(32 bits),用尖括號引用分隔開的32bit無符號整數: 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";

Sample Machine

理解設備樹怎么被用的最好辦法,就是做一遍,接下來就通過一步一步構建描述一個簡單machine的device tree來理解設備樹。假設machine的硬件配置如下:

由簡單到復雜的dts文件的例子:

 

  • 一個32bit的ARM CPU
  • 處理器的local bus的內存映射分布了串口、spi總線控制器、i2c控制器、中斷控制器 和 外部總線橋:
  •   256MB的SDRAM,基地址為0
  •   2個串口基地址為:0x101f1000 和 0x101f2000
  •   GPIO控制器,基地址為0x101f3000
  •   spi控制器,基地址為0x10170000,從屬設備:
      •   MMC slot with ss pin attached to GPIO #1 (不能很好理解其意思,所以就不胡亂翻譯了)
  •   External bus橋,從屬設備:
      •   smc smc91111 Ethernet設備,基地址為0x10100000
      •   i2c控制器,基地址為0x10160000,從屬設備:                   // i2c控制器,外邊再接上iic器件就是附加的原本不屬於cpu,掛載在iic控制器下的額外總線
      •   Maxim DS1338時鍾芯片,從設備I2C地址 1101000(0x58)
      •   64MB Nor flash,基地址為0x30000000

 

//提示: 看    " // "  提示來逐步學習理解 ; 

//學習思路 :  采用逐項遞增方式講解 (知識點疊加) ;

初始化結構

首先,為machine創建一個框架結構,一個有效設備樹的最簡單的結構,如下:

/ { compatible = "acme,coyotes-revenge"; }; 

compatible指定系統的名字,格式: compatible = "< manufacturer>,< model>"(制造商,型號)。它非常重要,用來精確指定設備,並通過包含manufacurer(制造商)名字來避免沖突。因為操作系統通過compatible的值來決定machine怎么運行,所以使用正確的值是非常重要的。   理論上來說,compatible是操作系統所有數據標示machine的唯一標示符,os將通過頂層compatible尋找相應的值。

CPUs

第二步,描述CPU的"cpus"節點,其包含每一個CPU描述信息的子節點,在這個例子中,CPU為一個雙核的arm cortex A9處理器,所以其描述如下:

/ { compatible = "acme,coyotes-revenge"; 
  //遞增添加內容1: cpus { cpu@0 { compatible = "arm,cortex-a9"; // 格式同頂層節點,<manufacturer>,<model> }; cpu@1 { compatible = "arm,cortex-a9"; }; }; };

節點名

每一個節點必須有一個節點名,格式: < name>[@< unit-address>]。

  • < name>:為最長31個字符的ascii字符串,一般用其代表的設備類型命名,ie. 一個3com Ethernet adapter的節點名:ethernet,不用3com509。
  • unit-address: 描述設備的地址,一般情況下,其提供訪問設備的基地址,節點的reg property也用此參數,見下文。

同層次兄弟節點的節點名必須是獨一無二的,不過多個節點可以使用一樣的通用name,只要地址不同就可以了。ie. serial@101f1000 & serial@101f2000

Devices

每一個device在系統中由一個設備樹節點描述,所以接下來,第三步是為設備填充樹的節點。不過,現在我為新節點創建一個空節點,直到我們知道地址范圍和如何處理irqs請求之后再填寫相應內容。如下:

/ { compatible = "acme,coyotes-revenge"; cpus { cpu@0 { compatible = "arm,cortex-a9"; }; cpu@1 { compatible = "arm,cortex-a9"; }; }; 
  //在上面內容的基礎上繼續遞加2,下面的內容繼續遞加依此類推 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"; }; }; };

在此tree中,在系統中為每一個device增加了節點,其層次結構反應了系統中的連接情況。ie. extern bus上的的設備憋創建為external bus節點的子節點,i2c設備被創建為i2c總線控制器的子節點。簡單來說,tree中的層次結構代表了系統中的CPU視圖

//說明(1):

目前,這個tree是無效的,因為它沒有設備之間的連接信息,接下來再添加這些信息。 在這個tree中有幾點需要注意:

  • 每個設備節點都有一個compatible屬性
  • flash節點的compatible屬性有兩個字符串值。
  • 如前所述,節點名反映設備類型,而非詳細型號。

compatible詳解

設備樹中每個節點都需要有compatible屬性,compatible屬性決定每一個設備驅動綁定哪一個設備。如上所介紹,compatible是一個字符串序列,第一個字符串指定精確設備,第二字符串指定兼容設備。

例如:Freescale MPC8349片上有一個根據國家半導體ns16550接口實現的串行設備,定義為:compatible = "fsl,mpc8349-uart", "ns16550". 第一個字符串指定精確設備,第二個指定國家半導體16550 uart兼容設備。

ns16550沒有制造商前綴(manufacturer)純屬歷史原因,所有的compatible值應該帶有制造商前綴。

這種做法允許將存在的設備驅動綁定到一類更新的設備,並且仍然能識別到精確的設備。

警告:不要使用通配符賦值,如:"fsl,mpc83xx-uart"等。為了兼容后續設備,一般會選擇一個特定實現,如上的:"ns16550"。

//說明(2):可尋址設備要添加reg屬性及相關的父節點的cell限制

( 還是繼續疊加理解  <1.> cpu ,和內存 , 以及xxx寄存器是不一樣的;  <2.> gpio 的 reg屬性可以分成幾部分比如:本例子里是兩個部分 )

設備尋址

關於設備尋址,設備樹中通過如下屬性encode地址信息:

reg :每個可尋址的設備有一個reg cells.
格式:reg = <address1 length1 [address2 length2] [address3 length3] ... > // 因為地址和地址長度是變量,所以父節點中定義#address-cells和#size-cells兩個屬性,聲明每個域里會用到多少cell #address-cells #size-cells
CPU尋址

CPU節點尋址是尋址里面最簡單的,每個CPU被一個獨一無二的ID標記,沒有size( 怎么理解????,可能僅僅是一個標記表示哪個cpu而已不需要像內存有地址范圍:而且還可以讀寫數據 )與CPU ids關聯

如下:

 cpus { #address-cells = <1>; #size-cells = <0>; // 此兩個屬性表明子節點reg 值為一個沒有size的uint32地址 ???? cpu@0 { compatible = "arm,cortex-a9"; reg = <0>; // 值與節點名的unit-address相同 }; cpu@1 { compatible = "arm,cortex-a9"; reg = <1>; // 同上 }; }; 

如果一個節點有reg屬性( 可尋址 ),則節點名必須包含unit-address  ( ...@第一個address值 ),並且取reg屬性的第一個address值。

有內存映像地址的設備

與cpu中只有address值不同,有內存映像地址的設備還需分配地址范圍值,每個子節點reg元素定義地址長度值的數量由父節點的#size-cells指定。如下:

/ { #address-cells = <1>; // 值為 1 cell(32bits) #size-cells = <1>; // 每個長度值為 1 cell // 如果是64 bit machines, 則以上兩值為2 ... serial@101f0000 { compatible = "arm,pl011"; reg = <0x101f0000 0x1000 >; // 第一個參數為基地址,第二個參數為地址長度,此處表示serial的內存地址范圍:0x101f0000~0x101f0fff }; serial@101f2000 { compatible = "arm,pl011"; reg = <0x101f2000 0x1000 >; }; gpio@101f3000 { compatible = "arm,pl061"; reg = <0x101f3000 0x1000 0x101f4000 0x0010>; // GPIO設備被分配到 兩個 地址范圍 }; interrupt-controller@10140000 { compatible = "arm,pl190"; reg = <0x10140000 0x1000 >; }; spi@10115000 { compatible = "arm,pl022"; reg = <0x10115000 0x1000 >; }; ... }; 

//說明(3) : 沒有和cpu直接連接的設備,而是通過某條總線和cpu相連的.

當然,並不是所有設備都直接和cpu相連,也有一些設備通過掛載到一條總線上和cpu相連。對於掛接到總線的設備,每個父節點為子節點定義地址域,如下:

external-bus { // 父節點; #address-cells = <2> // 子節點有2 cells基地址值,一個用於指定 chip number一個用於指定選中芯片基地址的偏移量; #size-cells = <1>; // 子節點有1 cell 地址長度; 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>; }; }; 

由於地址域被節點其子節點一起定義,所以父節點可以為總線定義任何尋址方式。除了直接父親以外的所有節點和子節點都不用關心本地的尋址域,不用關心地址從哪映射到哪( ???? ) 

如不明白,請繼續往下看,相信接下來的部分會幫你解惑

 

//說明(4) :無內存映像的設備,沒有直接訪問cpu的權限,此處的rtc.

 

無內存映像的設備

無內存映像的設備沒有直接訪問cpu的權限,父設備的驅動將間接訪問cpu,其cpu一樣reg屬性會有一個地址值,但沒有地址長度或范圍,如下:

//本例子中 rtc的父節點應該就是 i2c了.
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>; }; };

//說明(5) : 前面是分配本地地址,此處才說怎樣映射到cpu能直接訪問的地址.

(繼續疊加理解學習)

地址轉換

前面講了怎么給設備分配本地地址,但沒有說明怎么映射到cpu能直接訪問的地址。接下來就詳細分析一下這一部分:

根節點描述cpu地址空間視圖,根節點的子節點不需要做任何顯性的映射直接使用cpu的地址域。比如:serial@101f0000直接分配到地址0x101f0000.

而不是根節點的直接孩子的節點不使用cpu的地址域,為了能將其映射到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 0, Ethernet 1 0 0x10160000 0x10000 // Chipselect 1, i2c controller 2 0 0x30000000 0x10000000>; // Chipselect 2, NOR Flash,此處參考文章地址空間大小少一個0,但我覺得不對,所以自己做了修改,下同,就不再說明 // 相信大家直接通過這個列表就能知道地址怎么轉換的了,如下: 
( 映射到一個地址范圍 , ranges本身就是范圍的意思 ,這是關鍵 ) 1. 偏移量為 0 的Chipselect 0 映射到 0x10100000~0x1010ffff 2. 偏移量為 0 的Chipselect 1 映射到 0x10160000~0x1016ffff 3. 偏移量為 0 的Chipselect 2 映射到 0x30000000~0x3fffffff (此處參考文章寫的0x10000000,但我覺得應該是0x3fffffff,原地址見博文最后引用) ethernet@0,0 { compatible = "smc,smc91c111"; reg = <0 0 0x1000>; }; //i2c總線節點沒有ranges參數,因為i2c總線上的設備不需映射到cpu地址域,cpu直接通過i2c就能訪問i2c設備 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 0x10000000>; // 此處參考文章寫的0x4000000, 但我覺得為0x10000000 - 256MB }; }; };

ranges參數的值是一個地址轉換列表,每一個條目由如下幾部分組成:

  • 子節點地址:由子節點的#address-cells值決定
  • 父節點地址:由父節點的#address-cells值決定
  • 子節點地址空間的大小 :由子節點的#size-cells值決定

如果ranges參數為空,則表示子節點地址和父節點地址1:1映射。你可能會有疑問,既然1:1映射,為什么還要通過地址轉換來獲得地址空間。一些總線(比如PCI)有完全不同的地址空間細節需要暴露給操作系統。其他帶DMA的設備需要知道設備在總線上的真實地址。有時設備需要組合在一起去共享相同的可編程物理地址映射。是否需要通過1:1映射依賴於操作系統和硬件設計的很多信息。

缺乏ranges參數意味着,一個設備只能被其父節點訪問而不能被cpu直接訪問

 

 

//說明(6) :加入了中斷控制器節點,以及中斷源和中斷標志的添加.

 

中斷

中斷信號可以來自machine的任何設備,中斷信號在設備樹中被描述為節點之間的links。主要有如下4中屬性:

  • interrupt-controller:一個空屬性(不用賦值),表明定義的節點為中斷控制器;
  • #interrupt-cells: 表明連接此中斷控制器的interrupts屬性cell大小(類似於#address-cells和#size-cells);
  • interrupt-parent:指定節點設備所依附的中斷控制器的phandle,若沒有此參數,則從父節點繼承
  • interrupts:中斷說明符列表,節點通過此方法指定中斷號、觸發方式等;

一個中斷說明符描述指定中斷輸入設備的相關信息,由#interrupt-cells指定中斷說明符cell數量。設備可能一個或多個中斷源。一個中斷設備的說明符完全取決於綁定的中斷控制器設備。 定義一個中斷源需要多少cells由中斷控制器決定。加入中斷相關屬性后如下

/ { compatible = "acme,coyotes-revenge"; #address-cells = <1>; #size-cells = <1>; interrupt-parent = <&intc>; // intc->interrupt-controller,作為系統默認的interrupt-parent屬性,子節點重寫則覆蓋 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>;       // 中斷說明符有2 cells,此例中cell 1表示中斷號,cell 2 表示觸發方式 }; spi@10115000 { compatible = "arm,pl022"; reg = <0x10115000 0x1000 >; interrupts = < 4 0 >; // 注:設備還可以使用多個中斷號,假如此spi使用兩個,則:interrupts = <0 4 0>, <1 5 0>; }; external-bus { #address-cells = <2> #size-cells = <1>; ranges = <0 0 0x10100000 0x10000 // Chipselect 0, Ethernet 1 0 0x10160000 0x10000 // Chipselect 1, i2c controller 2 0 0x30000000 0x10000000>; // Chipselect 2, 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 0x10000000>; }; }; };

//本實例中這個cell的理解還是有點不清楚 ???? 后邊再繼續看 ????

另, 關於cell含義在內核中的相關文檔有詳細描述,比如arm gic 中斷:

# Documentation/devicetree/bindings/arm/gic.txt The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI interrupts. The 2nd cell contains the interrupt number for the interrupt type. SPI interrupts are in the range [0-987]. PPI interrupts are in the range [0-15]. The 3rd cell is the flags, encoded as follows: bits[3:0] trigger type and level flags. 1 = low-to-high edge triggered 2 = high-to-low edge triggered 4 = active high level-sensitive 8 = active low level-sensitive bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of the 8 possible cpus attached to the GIC. A bit set to '1' indicated the interrupt is wired to that CPU. Only valid for PPI interrupts. 

//說明(7):特有數據 , 特殊節點 : aliases節點 , chosen節點 , DTC.

設備特有數據

除了上面講的常用屬性,任意需要的屬性和子節點都可以被加入到設備樹,不過新device-specific屬性應將制造商名作為前綴命名,以避免與標准的屬性沖突

其實還有一些要求,不過主要針對內核開發者的,而我還沒有那個水平,就沒詳細看了

特殊節點

aliases節點

一個specific節點通常以完全路徑的形式引用,如:/external-bus/ethernet@0,0 , 但是這樣太復雜了,不利於閱讀。所以通常會用以一個短的別名命名的aliases節點去指定設備的完全路徑,如下:

aliases { ethernet0 = &eth0; serial0 = &serial0; }; 

注:property = &Label 不同於如上中斷phandle引用的phandle = <&Lable>

chosen節點

chosen節點不指明真實的設備,其為硬件和操作系統數據傳輸服務,如:啟動參數。通常chosen節點在dts源文件中寫為空,在啟動時再填充,在例中增加如下:

chosen { bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200"; }; 

DTC (device tree compiler)

DTC將.dts編譯為.dtb的工具。DTC的源代碼位於內核的scripts/dtc目錄,在Linux內核使能了Device Tree的情況下,編譯內核的時候主機工具dtc會被編譯出來,對應scripts/dtc/Makefile中的“hostprogs-y := dtc”。 在Linux內核的arch/arm/boot/dts/Makefile中,描述了當某種SoC被選中后,哪些.dtb文件會被編譯出來,如與VEXPRESS對應的.dtb包括:

dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \ vexpress-v2p-ca9.dtb \ vexpress-v2p-ca15-tc1.dtb \ vexpress-v2p-ca15_a7.dtb \ xenvm-4.2.dtb 

在Linux下,我們可以通過make dtbs命令單獨編譯Device Tree文件。因為arch/arm/Makefile中含有一個dtbs編譯target,如下:

# Build the DT binary blobs if we have OF configured ifeq ($(CONFIG_USE_OF),y) KBUILD_DTBS := dtbs endif

Device Tree Blob (dtb)

dtb是dts被DTC編譯后生成的二進制格式Device Tree描述,可由Linux內核解析。系統設計時通常會單獨留下一個很小的flash空間存放.dtb文件,bootloader在引導kernel的過程中,會先讀取該.dtb到內存

Binding

對於Device Tree中的結點和屬性具體是如何來描述設備的硬件細節的,內核里有相應的文檔,位於:Documentation/devicetree/bindings目錄,其下又分為很多子目錄。

 

 

//說明(8): dts編譯文件的解析.

 

dts解析API

注:此部分基本完全摘自參考文檔

在Linux的BSP和驅動代碼中,解析dts的API通常被以“of_”作為前綴,它們的實現代碼位於內核的drivers/of目錄。接下來就介紹一下常用的API。

int of_device_is_compatible(const struct device_node *device,const char *compat);

判斷設備結點的compatible 屬性是否包含compat指定的字符串。當一個驅動支持2個或多個設備的時候,這些不同.dts文件中設備的compatible 屬性都會進入驅動 OF匹配表。因此驅動可以透過Bootloader傳遞給內核的Device Tree中的真正結點的compatible 屬性以確定究竟是哪一種設備,從而根據不同的設備類型進行不同的處理。如drivers/pinctrl/pinctrl-sirf.c即兼容於"sirf,prima2-pinctrl",又兼容於"sirf,prima2-pinctrl",在驅動中就有相應分支處理:

if (of_device_is_compatible(np, "sirf,marco-pinctrl")) is_marco = 1; struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible);

根據compatible屬性,獲得設備結點。遍歷Device Tree中所有的設備結點,看看哪個結點的類型、compatible屬性與本函數的輸入參數匹配,大多數情況下,from、type為NULL。

int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz); int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz); int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz); int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value);

讀取設備結點np的屬性名為propname,類型為8、16、32、64位整型數組的屬性。對於32位處理器來講,最常用的是of_property_read_u32_array()。如在arch/arm/mm/cache-l2x0.c中,透過如下語句讀取L2 cache的"arm,data-latency"屬性:

of_property_read_u32_array(np, "arm,data-latency", data, ARRAY_SIZE(data)); 

在arch/arm/boot/dts/vexpress-v2p-ca9.dts中,含有"arm,data-latency"屬性的L2 cache結點如下:

L2: cache-controller@1e00a000 {  
        compatible = "arm,pl310-cache"; reg = <0x1e00a000 0x1000>; interrupts = <0 43 4>; cache-level = <2>; arm,data-latency = <1 1 1>; arm,tag-latency = <1 1 1>; } 

有些情況下,整形屬性的長度可能為1,於是內核為了方便調用者,又在上述API的基礎上封裝出了更加簡單的讀單一整形屬性的API,它們為int of_property_read_u8()、of_property_read_u16()等,實現於include/linux/of.h:

static inline int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value) { return of_property_read_u8_array(np, propname, out_value, 1); } static inline int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value) { return of_property_read_u16_array(np, propname, out_value, 1); } static inline int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value) { return of_property_read_u32_array(np, propname, out_value, 1); } 

int of_property_read_string(struct device_node np, const char *propname, const char *out_string); int of_property_read_string_index(struct device_node np, const char *propname, int index, const char *output);

前者讀取字符串屬性,后者讀取字符串數組屬性中的第index個字符串。如drivers/clk/clk.c中的of_clk_get_parent_name()透過of_property_read_string_index()遍歷clkspec結點的所有"clock-output-names"字符串數組屬性。

const char *of_clk_get_parent_name(struct device_node *np, int index) { struct of_phandle_args clkspec; const char *clk_name; int rc; if (index < 0) return NULL; rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index, &clkspec); if (rc) return NULL; if (of_property_read_string_index(clkspec.np, "clock-output-names", clkspec.args_count ? clkspec.args[0] : 0, &clk_name) < 0) clk_name = clkspec.np->name; of_node_put(clkspec.np); return clk_name; } EXPORT_SYMBOL_GPL(of_clk_get_parent_name); 

static inline bool of_property_read_bool(const struct device_node *np, const char *propname);

如果設備結點np含有propname屬性,則返回true,否則返回false。一般用於檢查空屬性是否存在。

void __iomem *of_iomap(struct device_node *node, int index); 通過設備結點直接進行設備內存區間的 ioremap(),index是內存段的索引。若設備結點的reg屬性有多段,可通過index標示要ioremap的是哪一段,只有1段的情況,index為0。采用Device Tree后,大量的設備驅動通過of_iomap()進行映射,而不再通過傳統的ioremap。

unsigned int irq_of_parse_and_map(struct device_node *dev, int index); 透過Device Tree或者設備的中斷號,實際上是從.dts中的interrupts屬性解析出中斷號。若設備使用了多個中斷,index指定中斷的索引號。 還有一些OF API,這里不一一列舉,具體可參考include/linux/of.h頭文件。

高通Android源碼中dts文件

AndroidBoard.mk

Android編譯過程(如想了解更多可參考:Android編譯過程詳解)中會解析到device\qcom\msm8916_32\AndroidBoard.mk,此文件中選擇了kernel的默認配置文件,如下:

# device\qcom\msm8916_32\AndroidBoard.mk #---------------------------------------------------------------------- # Compile (L)ittle (K)ernel bootloader and the nandwrite utility #---------------------------------------------------------------------- ifneq ($(strip $(TARGET_NO_BOOTLOADER)),true) # Compile include bootable/bootloader/lk/AndroidBoot.mk $(INSTALLED_BOOTLOADER_MODULE): $(TARGET_EMMC_BOOTLOADER) | $(ACP) $(transform-prebuilt-to-target) $(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_BOOTLOADER_MODULE) droidcore: $(INSTALLED_BOOTLOADER_MODULE) endif #---------------------------------------------------------------------- # Compile Linux Kernel #---------------------------------------------------------------------- ifeq ($(KERNEL_DEFCONFIG),) KERNEL_DEFCONFIG := msm8916_defconfig //選擇msm8916_defconfig文件為默認配置文件 endif include kernel/AndroidKernel.mk $(INSTALLED_KERNEL_TARGET): $(TARGET_PREBUILT_KERNEL) | $(ACP) $(transform-prebuilt-to-target)

msm8916_defconfig

此文件中主要是一些編譯開關,包括dts文件的編譯開關,如下:

# kernel\arch\arm\configs\msm8916_defconfig ... CONFIG_ARCH_MSM=y CONFIG_ARCH_MSM8916=y // dts文件的編譯開關,當然也在其他地方用到,如加載板級文件:obj-$(CONFIG_ARCH_MSM8916) += board-8916.o ...

Makefile

dts文件目錄的mk文件決定需要加載哪些dts文件,這些文件最終打包到dt.img,再經由mkbootimg工具和其他鏡像一起打包到boot.img。關鍵源碼如下:

# kernel\arch\arm\boot\dts\qcom\Makefile ... // 我們的代碼針對每一個項目新建了一個dts文件,然后通過此文件去include了相關dts文件,所以下面都被屏蔽掉了 dtb-$(CONFIG_ARCH_MSM8916) += msm8916-qrd-skuh-$(OEM_PROJECT_NAME).dtb #msm8916-sim.dtb #msm8916-rumi.dtb #msm8916-cdp.dtb #msm8916-cdp-smb1360.dtb #msm8916-mtp.dtb #msm8916-512mb-mtp.dtb #msm8916-mtp-smb1360.dtb #msm8916-512mb-mtp-smb1360.dtb #msm8916-512mb-qrd-skui.dtb #msm8916-qrd-skuh.dtb #msm8916-qrd-skuhf.dtb #msm8916-qrd-skui.dtb #msm8916-512mb-qrd-skuh.dtb #msm8939-sim.dtb #msm8939-rumi.dtb #msm8939-qrd-skuk.dtb #msm8939-cdp.dtb #msm8939-cdp-smb1360.dtb #msm8939-mtp.dtb #msm8939-mtp-smb1360.dtb ...

dts中的platform info

msm8916-cdp.dts文件中定義平台信息,如下:

# kernel\arch\arm\boot\dts\qcom\msm8916-cdp.dts #include "msm8916-cdp.dtsi" #include "msm8916-memory.dtsi" / { model = "Qualcomm Technologies, Inc. MSM 8916 CDP"; compatible = "qcom,msm8916-cdp", "qcom,msm8916", "qcom,cdp"; qcom,board-id = <1 0>; }; ... 

不過我們在每個項目的dts文件中重新定義了平台信息,如下:

# kernel\arch\arm\boot\dts\qcom\msm8916-qrd-skuh-$(OEM_PROJECT_NAME).dts #include "msm8916-qrd-skuh.dtsi" #include "msm8916-memory.dtsi" / { model = "Qualcomm Technologies, Inc. MSM 8916 QRD SKUH changcheng l783"; compatible = "qcom,msm8916-qrd-skuh", "qcom,msm8916-qrd", "qcom,msm8916", "qcom,qrd"; qcom,board-id = <0x1000b 0> , <0x1000b 4>; }; ... 

Reference

我的這篇博文只是寫了一些基本的東西,主要參考下面這些文檔,並且很多內容直接翻譯自下面的文檔,如果想了解更多請查閱如下引用文檔: kernel\Documentation\devicetree源碼中的文檔,很有參考價值,其實需要的基本能在里面找到,我已上傳至百度雲,可以click下載查看 http://devicetree.org/Device_Tree_Usage :很多內容譯自此處Power_ePAPR_APPROVED_v1.0.pdf進階文檔,因為官網總是不能成功訪問,所以在我百度網盤存了一份,分享給大家http://blog.csdn.net/21cnbao/article/details/8457546


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM