设备树总结
參考:
https://blog.csdn.net/ethercat_i7/article/details/83786670
https://www.cnblogs.com/god-of-death/archive/2004/01/13/10270453.html
https://zhuanlan.zhihu.com/p/143167176
一、基本概念
DTS即Device Tree Source,是一個文本形式的文件,用于描述硬件信息,包括CPU的數量和類別、內存基地址和大小、中斷控制器、總線和橋、外設、時鐘和GPIO控制器等。
DTB即Device Tree Blob,是一個二進制形式的文件,由linux內核識別,為其中的設備匹配合適的驅動程序。
DTC即Device Tree Compiler,將適合人類閱讀和編輯的DTS文件編譯成適合機器處理的DTB文件。
DTS(Device tree syntax,另一種說法是Device tree source)是設備樹源文件,為了方便閱讀及修改,采用文本格式。DTC(Device tree compiler)是一個小工具,負責將DTS轉換成DTB(Device tree blob)。DTB是DTS的二進制形式,供機器使用。使用中,我們首先根據硬件修改DTS文件,然后在編譯的時候通過DTC工具將DTS文件轉換成DTB文件,然后將DTB文件燒寫到機器上(如emmc,磁盤等存儲介質)。系統啟動時,fastboot(或者類似的啟動程序,如Uboot)在啟動內核前將DTB文件讀到內存中,跳轉到內核執行的同時將DTB起始地址傳給內核。內核通過起始地址就可以根據DTB的結構解析整個設備樹。說設備樹的規范可以分成兩個層次,是針對DTS的,關于DTB的結構不在此范圍內。DTB僅僅是為了方便機器使用而對DTS的轉換而已(也可以說DTS僅是為了方便人類使用而對DTB的一種描述)。
如上圖所示,bootloader讀取dtb文件放入RAM中,并將存放地址告訴linux內核,內核啟動以后從該地址讀取相應的設備信息,匹配平臺和設備驅動。
設備樹(Device tree)是一套用來描述硬件屬相的規則。ARM Linux采用設備樹機制源于2011年3月份Linux創始人Linus Torvalds發的一封郵件,在這封郵件中他提倡ARM平臺應該參考其他平臺如PowerPC的設備樹機制描述硬件。因為在此之前,ARM平臺還是采用舊的機制,在kernel/arch/arm/plat-xxx目錄和kernel/arch/arm/mach-xxx目錄下用代碼描述硬件,如注冊platform設備,聲明設備的resource等。因為這些代碼都是用來描述芯片平臺及板級差異的,所以對于內核來講都是垃圾代碼。
設備樹是從軟件使用的角度描述硬件的,不是從硬件設計的角度描述的。我們在寫設備樹時沒有必要按照硬件邏輯生搬硬套,也不要指望通過閱讀設備樹弄清楚硬件是如何設計的。對于軟件可以自動識別的硬件,如USB設備,PCI設備,也是沒有必要通過設備樹描述的。
本質上,Device Tree改變了原來用code方式將HW配置信息嵌入到內核代碼的方法,改用bootloader傳遞一個DB的形式。對于嵌入式系統,在系統啟動階段,bootloader會加載內核并將控制權轉交給內核。
設備樹首先是一個樹形結構,并且是一棵樹。除了根節點外其他子節點都有唯一的父節點,節點下可以有子節點和屬性(子節點可以看成是樹枝,屬性可以看成是葉子)。屬性由名字和值組成(名字是必須的,但是值不是必須的,如果只要根據是否存在這個屬性就可以表示我們想要的功能,那么可以不需要有值)。節點下的屬性用來表示節點的特性,子節點和父節點具有一定的從屬關系。真實的硬件不可能是這樣規則的樹形結構,所以設備樹僅是軟件開發人員為了描述硬件而做的一個近似表示而已,連抽象都算不上。
二、節點
首先說節點的表示方法,除了根節點只用一個斜杠“/”表示外,其他節點的表示形式如“node-name@unit-address”。@前邊是節點名字,后邊是節點地址。節點名字的長度范圍是1到31。
規范要求節點名字應該以字母開頭,雖然允許后邊的位置使用非字母的字符,但實際情況我們實在沒必要使用其他字符,一般情況全部用字母表示就夠了。特別是規范建議在起名字時采用通用的名字而不是專有的名字,比如對于網卡,使用ethernet表示就可以了,可以通過地址區分不同的網卡,網卡的區別可以通過節點下的屬性區分。
節點地址是用來區別同名節點的,不是軟件意義上的地址,但是有些情況可以用軟件地址作為這個地址。比如兩個I2C控制器的名字可以都是i2c,然后用控制器寄存器首地址作為節點地址。對于cpu,因為它是沒有寄存器地址的,就可以用核的號碼作為地址,對于8核處理器,地址可以從0到7。ePAPR規范中關于節點地址的描述不太好理解,原文是“The unit-address component of the name is specific to the bus type on which the node sits”。其實我覺得這句話說的也不太準確,因為并不是所有節點表示的硬件都位于某個總線上,比如內存,cpu。設備樹是軟件對硬件的一種近似表示,軟件需要他怎么表示,他就怎么表示。對于cpu,軟件需要序號,那么地址就用序號,對于i2c控制器,軟件需要寄存器首地址,那么就用地址。
除此之外規范還要求,如果節點有地址,那么節點下邊必須有一個叫reg的屬性,并且該地址必須和reg的屬性的第一個地址相同。如果節點沒有reg屬性,那么節點地址及前邊的@必須都不能有。
關于屬性和值我們還沒開始介紹,這里插一句,其實reg就是register的縮寫,屬性主要用來表示控制器寄存器首地址的。我覺得規范中這一條不是非常有必要,因為有些設備只要有一個地址就夠了,那么放在節點地址中就夠了,完全沒必要非得再加個reg屬性。在這一段的最后一句,規范來了句“The binding for a particular bus may specify additional, more specific requirements for the format of reg and the unit-address.”,我覺得這句話的意思和“設備樹是軟件對硬件的一種近似表示,軟件需要他怎么表示,他就怎么表示”差不多,沒有什么玄的。
節點路徑也比較容易理解,從根節點到每個節點都可以形成一個路徑,如第一節的例子/cpus/cpu@0,通過這個可以唯一的表示cpu@0這個節點。因為cpu@0在cpus下邊是唯一的,但是在整個設備樹可能不是唯一的,只有用全路徑表示才能毫無異議的確認是哪個節點。如果省略了節點地址也不會產生歧義,那么可以省略不寫。就像編程中的括號一樣,個人覺得這個沒必要省。
除了名字和地址外,節點前邊還可以有一個標簽(label),這個標簽不是必須的,一般只有在別個地方需要引用這個節點時才會用標簽標示這個節點,因為如果用全路徑太繁瑣了。如“i2c_1: i2c@12C70000”中的i2c_1就是一個標簽。
三、屬性
device_type = "memory"就是一個屬性,等號前邊是屬性,后邊是值。節點是一個邏輯上相對獨立的實體,屬性是用來描述節點特性的,根據需要一個節點由0個,1個或多個屬性表示節點的特性。一個屬性由名字和值兩部分組成。
和節點的名字類似,規范要求屬性名字由1到31個字符組成。和節點名字字符的種類有些區別,不允許有大寫字母,增加了問號和井號兩個字符。不清楚為什么沒有和節點名字完全保持一致,井號對于初學者容易誤解,以為是注釋。
屬性的值在內存中由0個或多個字節存儲。標準定義的基本類型包括:空,u32,u64,字符串,prop-encoded-array,字符數組6種。前邊我們已經提到,當不需要值就可以表示節點的特性時,屬性的值可以為空。u32,u64,字符串,字符數組和c語言的定義沒有區別,注意的是規范要求都是大端表示,字符串也是以0x00結尾。prop-encoded-array是一個結構體數組,數組的元素具體是什么根據屬性的定義確定,后邊我們講到具體的屬性時會詳細說明。規范中還有一個類型的屬性值,叫phandle,這個類型的屬性在內存中存儲時本質上是u32。
規范預定義了一些標準的屬性。“compatible”,“model”,"device_type"都是用來表示節點基本信息的。
“compatible”屬性是用來匹配驅動的,它的類型是字符串數組,每個字符串表示一種設備的類型,從具體到一般。舉個例子就比較清楚了,比如某個串口控制器節點的屬性”compatible = “fsl,mpc8641-uart”, “ns16550"“。第一個字符串“fsl,mpc8641-uart”前邊部分是廠商(推測是frescale),后邊部分是控制器具體型號,這個形式也是規范建議的標準寫法。第二個字符串ns16550表示一類符合同一標準的串口控制器,比第一個字符串表示的范圍更大。內核匹配驅動時首先看是否有匹配第一個字符串的驅動,如果沒有的話再匹配第二個(如果有更多的,依次類推,所以優先匹配前邊的)。
"model"屬性用來表示設備的型號,用字符串表示,不像"compatible"用多個字符串,只需一個就夠了。"device_type"屬性用來表示設備類型,用字符串表示。
“#address-cells”,"#size-cells",“reg”,“ranges”,"dma-ranges"屬性都是和地址有關的。
不同的平臺,不同的總線,地址位長度可能不同,有32位地址,有64位地址,為了適應這個,規范規定一個32位的長度為一個cell。#address-cells屬性用來表示總線地址需要幾個cell表示,該屬性本身是u32類型的。#size-cells屬性用來表示子總線地址空間的長度需要幾個cell表示,屬性本身的類型也是u32。可以這么理解父節點表示總線,總線上每個設備的地址長度以及地址范圍是總線的一個特性,用#address-cells和#size-cells屬性表示,比如總線是32位,那么#address-cells設置成1就可以了。這兩個屬性不可以繼承,就是說在未定義這兩個屬性的時候,不會繼承更高一級父節點的設置(應該是不會對下一級生效),如果沒有設置的話,內核默認"#address-cells"為2,"#size-cells"為1。
reg屬性用來表示節點地址資源的,比如常見的就是寄存器的起始地址及大小。要想表示一塊連續地址,必須包含起始地址和空間大小兩個參數,如果有多塊地址,那么就需要多組這樣的值表示。還記得前邊說過的prop-encoded-array類型的屬性吧,就是用來干這個的,他表示一個數組,每個元素的具體格式根據屬性而定,reg屬性的每個元素是一個二元組,包含起始地址和大小。還有另外一個問題,地址和大小用幾個u32表示呢?這個就由父節點的"#address-cells","#size-cells"屬性確定。
總線上設備在總線地址和總線本身的地址可能不同,ranges屬性用來表示如何轉換。和reg屬性類似,ranges屬性也是prop-encoded-array類型的屬性,不同的是ranges屬性的每個元素是三元組,按照前后順序分別是(子總線地址,父總線地址,大小)。子總線地址需要幾個u32表示由ranges屬性所在節點(當前節點)的#address-cells屬性決定,父總線地址需要幾個u32表示由上一級節點的#address-cells屬性決定,大小需要幾個u32表示由當前節點的’#size-cells’屬性確定。
根節點是從cpu的角度來描述地址空間的,根節點的子節點總是依賴于cpu的地址域,所以不需要顯示地地址映射。比如, serial@101f0000 設備描述的地址就是0x101f0000.
但是有些節點并不是根節點的直接子節點,因此其不使用cpu地址域。為了可以得到設備的映射地址,設備樹必須說明一個域到另一個作用域的內存地址的轉換關系,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, Ethernet1 0 0x10160000 0x10000 // Chipselect 2, i2ccontroller2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash //zrw:第一列的0 1 2是片選號,和第二列共同組成子地址,應該順著寫就行;第三列是父節點基地址;第四列是子地址空間長度。這里的第一、二、四列分別對應下面各個reg的三列。ranges實際上做的就是把一些不在cpu地址空間的設備掛到了指定的cpu地址空間。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屬性是說明地址轉換的一個列表,列表的每一個條目分別表示子節點的地址,父節點的地址,子節點地址空間的長度(內存空間大小),每個條目的個數分別是用子節點的 #address-cells 值,父節點#address-cells 值,以及子節點 #size-cells 的值來表示。
· 比如,就上面出顯得設備樹代碼來講,子節點的#address-cells為2,父節點#address-cells為1,子節點#size-cells 為1,則上面代碼中程序先的ranges屬性的含義就是:
· 片選號為0,偏移量為0的設備映射的地址為0x10100000…0x1010ffff
· 片選號為1,偏移量為0的設備映射的地址為0x10160000…0x1016ffff
· 片選號為2,偏移量為0的設備映射的地址為0x30000000…0x30ffffff
有的時候在一個節點中需要引用另外一個節點,比如某個外設的中斷連在哪個中斷控制器上。在講節點那一節我們說過,可以通過節點的全路徑指定是哪個節點,但這種方法非常繁瑣。phandle屬性是專門為方便引用節點設計的,想要引用哪個節點就在該節點下邊增加一個phandle屬性,設定值為一個u32,如’phandle = <1>’,引用的地方直接使用數字1就可以引用該節點,如’interrupt-parent = <1>’。以上是規范中描述的方法,實際上這樣也不方便,我在實際的代碼中沒有看到這么用的。還記得節點那節說過節點名字前邊可以定義一個標簽吧,實際情況是都用標簽引用,比如節點標簽為intc1,那么用’interrupt-parent = <&intc1>'就可以引用了。
status屬性用來表示節點的狀態的,其實就是硬件的狀態,用字符串表示。‘okay’表示硬件正常工作,“disabled”表示硬件當前不可用,“fail”表示因為出錯不可用,“fail-sss”表示因為某種原因出錯不可用,sss表示具體的出錯原因。實際中,基本只用’okay’和’disabled’。
四、中斷
中斷一般包括中斷產生設備和中斷處理設備。中斷控制器負責處理中斷,每一個中斷都有對應的中斷號及觸發條件。中斷產生設備可能有多個中斷源,有時多個中斷源對應中斷控制器中的一個中斷,這種情況中斷產生設備的中斷源稱之為中斷控制器中對應中斷的子中斷。一般情況中斷產生設備數量要多于中斷控制器,多個中斷產生設備的中斷都由一個中斷控制器處理,這種多對一的關系也很像一個樹形結構,所以在設備樹中,中斷也被描述成樹,叫中斷樹。以下表述的時候為了明確是在說中斷樹,在父節點和子節點前邊我們都加上“中斷”二字,是為了防止和設備樹的父節點、子節點混淆(雖然大部分情況設備樹的父子關系就是中斷樹的父子關系,但是因為存在特例,所以我們還是強調是中斷父子關系)。
中斷產生設備用interrupts屬性描述中斷源(interrupt specifier),因為不同的硬件描述中斷源需要的數據量不同,所以interrupts屬性的類型也是prop-encoded-array。為了明確表示一個中斷源由幾個u32表示,又引入了#interrupt-cells屬性,#interrupt-cells屬性的類型是u32,假如一個中斷源需要2個u32表示(一個表示中斷號,另一個表示中斷類型),那么#interrupt-cells就設置成2。有些情況下,設備樹的父節點不是中斷的父節點(主要是中斷控制器一般不是父節點),為此引入了interrupt-parent屬性,該屬性的類型是phandle,用來引用中斷父節點(我們前邊說過,一般用父節點的標簽,這個地方說中斷父節點而不是中斷控制器是有原因的)。interrupt-parent屬性用來指定當前設備的Interrupt Controllers/Interrupt Nexus。如果設備樹的父節點就是中斷父節點,那么可以不用設置interrupt-parent屬性。interrupts屬性和interrupt-parent屬性都是中斷產生設備節點的屬性(類似于葉子),但是#interrupt-cells屬性不是,#interrupt-cells屬性是中斷控制器節點以及interrupt nexus節點的屬性(類似于樹根和樹枝,interrupt nexus應該是直接和中斷控制器相連,用來記錄連接到中斷控制器的屬性,中間層的感覺),這兩類節點都可能是中斷父節點。
中斷控制器節點用interrupt-controller屬性表示自己是中斷控制器,這個屬性的類型是空,不用設置值,只要存在這個節點就表示該節點是中斷控制器。除了這個屬性外,中斷控制器節點還有#interrupt-cells屬性,用來表示該中斷控制器直接管理下的interrupt domain(后邊我們會講中斷控制器的中斷子節點interrupt nexus節點有單獨的interrupt domain)用幾個u32表示一個中斷源(interrupt specifier)。中斷控制器節點就包括interrupt-controller和#interrupt-cells兩個關于中斷的屬性。中斷控制器的#address-cells屬性和中斷映射有關系,但是該屬性不是為中斷設計的,中斷映射只是用到了這個屬性而已。
前邊說中斷控制器中的一個中斷可能對應中斷產生設備中的多個中斷源,那這種對應關系用什么描述呢?我們還說過#interrupt-cell屬性不僅是中斷控制器節點的屬性,還是interrupt nexus節點的屬性,這個interrupt nexus節點就是描述中斷映射關系的,該節點通過interrupt-map,interrupt-map-mask屬性描述中斷映射關系。interrupt-map屬性是prop-encoded-array類型的,每個元素表示一個中斷映射關系(注意是一個"中斷映射關系",不是"一個中斷"映射關系),interrupt-map屬性從前向后包括:中斷子設備地址,中斷子設備中斷源(interrupt specifier),中斷父設備,中斷父設備地址,中斷父設備中斷源(interrupt specifier)五部分。中斷子設備地址具體由幾個u32組成是由中斷子設備所在總線(不是中斷父設備)的#address-cells屬性決定的,這個地方為什么用中斷設備地址而不用中斷設備的phandle,是有原因的,因為中斷設備會用interrupt-parrent屬性指向中斷父節點,所以中斷子設備是可以確定的,不需要說明。還因為中斷子設備地址可以做與運算,通過interrupt-map-mask屬性就可以實現多對一的映射。中斷子設備中斷源(interrupt specifier)由幾個u32組成是由該interrupt nexus節點下的#interrupt-cell決定的。中斷父設備是一個指向中斷父設備的phandle屬性,一般情況下是中斷控制器,但是按照中斷樹的邏輯,也可能是更高一級的interrupt nexus節點。中斷父設備地址具體由幾個u32組成是由中斷父設備節點下的#address-cells屬性決定的(注意,不是中斷父設備所在總線的#address-cells屬性)。中斷父設備中斷源(interrupt specifier)由幾個u32組成是由中斷父設備的#interrupt-cells屬性決定的。
還記得前邊說過中斷設備的中斷源和中斷控制器的中斷源可能是多對一的關系,如果每個子中斷都用interrupt-map中的一行表示,那么interrupt-map屬性將非常大。為了讓多個子中斷共享映射關系,引入了interrupt-map-mask屬性,該屬性的類型也是prop-enacoded-array,interrupt-map-mask屬性包含中斷子設備地址和中斷子設備中斷源的bit mask,給定一個子中斷源,那么首先和interrupt-map-mask做與運算,運算結果再通過interrupt-map屬性查找對應的中斷父設備中斷源。這就是我們前邊為什么說interrupt-map屬性的一行是一個“中斷映射關系”,而不是“一個中斷”映射關系的原因。
我們再來復習一下,整個中斷樹的最底層是中斷產生設備(也可能是從interrupt nexus節點),中斷產生設備用interrupts屬性描述他能產生的中斷。因為他的中斷父設備可能和設備樹的父設備不同,那么用interrupt-parent屬性指向他的中斷父設備。他的中斷父設備可能是中斷控制器(如果中斷產生設備的中斷和中斷控制器的中斷是一一對應的,或者最底層是interrupt nexus節點),也可能是interrupt nexus節點(如果最底層是中斷產生設備,且需要映射)。interrupt nexus節點及他的所有直接子節點構成了一個interrupt domain,在該interrupt domain下中斷源怎樣表示由#interrupt-cells屬性決定,如何由中斷子設備中斷源找到中斷父設備中斷源由interrupt-map和interrupt-map-mask屬性決定。interrupt nexus的父節點可能還是一個interrupt nexus父節點,也可能是一個中斷控制器,當向上找到最后一個中斷控制器,并且該中斷控制器再也沒有中斷父設備時,整個中斷樹就遍歷完成了。中斷控制器用interrupt-controller屬性表示自己是中斷控制器,并且用#interrupt-cells屬性表示他所直接管理的interrupt domain用幾個u32表示一個中斷源。根據中斷樹的特性,一個設備樹中是有可能有多個中斷樹的。
以上是中斷在設備樹中如何描述的規則,聽起來是挺復雜的,但只要理解了就很簡單,為了幫助理解我們舉一個實際的例子。為了突出中斷部分,我們做了簡化。
/ {model = "Marvell Armada 375 family SoC";compatible = "marvell,armada375";soc {#address-cells = <2>;#size-cells = <1>;interrupt-parent = <&gic>;internal-regs {compatible = "simple-bus";#address-cells = <1>;#size-cells = <1>;timer@c600 {compatible = "arm,cortex-a9-twd-timer";reg = <0xc600 0x20>;interrupts = <GIC_PPI 13 (IRQ_TYPE_EDGE_RISING | GIC_CPU_MASK_SIMPLE(2))>;clocks = <&coreclk 2>;};gic: interrupt-controller@d000 {compatible = "arm,cortex-a9-gic";#interrupt-cells = <3>;#address-cells = <0>;interrupt-controller;reg = <0xd000 0x1000>,<0xc100 0x100>;};}pcie-controller {compatible = "marvell,armada-370-pcie";#address-cells = <3>;#size-cells = <2>;pcie@1,0 {#address-cells = <3>;#size-cells = <2>;#interrupt-cells = <1>;interrupt-map-mask = <0 0 0 0>;interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;};}; }首先我們看到timer@c600這個設備節點下定義了interrupts屬性,這說明該設備可以產生中斷,但是這個屬性下描述了幾個中斷我們是看不出來的(如果有經驗了,我們能猜出只是一個中斷,現在我們按照規則確認)。因為該節點沒有interrupt-parent屬性,那么認為設備樹的父節點internal-regs就是中斷父節點,在internal-regs父節點下還是沒有interrupt-parent屬性,那么還是繼續找設備樹父節點,找到了soc,在該節點下邊有interrupt-parent屬性。該屬性引用的標簽為gic,搜索整個設備樹,interrupt-controller@d000的標簽為gic。gic節點下有interrupt-controller屬性,說明他是一個中斷控制器。gic節點還有屬性#interrupt-cells = <3>,說明在該控制器的interrupt domain下,中斷源(interrupt specifier)用3個u32表示,我們再看timer@c600下的interrupts屬性也確實由3個u32組成(可以參考GIC的規范,第一個u32表示中斷類型,第二個是中斷號,第三個是中斷觸發條件)。這個例子說明如果中斷產生設備的中斷源和中斷控制器的中斷源是一一對應的,那么可以不需要interrupt nexus節點及相關的屬性來表示中斷映射。
再看pcie@1,0這個節點,有#interrupt-cells屬性,但是沒有interrupt-controller屬性,這說明他是一個interrupt nexus節點。該節點的**#interrupt-cells屬性為1,說明該interrupt nexus節點管轄下的中斷源用1個u32表示就可以了**。在pcie@1,0節點下邊沒有子節點,且也沒有節點的interrupt-parent屬性指向pcie@1,0節點,所以從設備樹上看不到該interrupt domain下的中斷產生設備,可能的原因是這些中斷產生設備軟件可以動態識別所以不需要設備樹描述。因為interrupt-map-mask屬性是由中斷產生設備的地址和中斷源(interrupt specifier)組成,且中斷源用1個u32表示,那么可以推測中斷產生設備地址由3個u32組成。這里需要注意的是pcie@1,0節點的#address-cells屬性為3,是說該總線下邊的設備地址用3個u32表示,但并不代表中斷產生設備的設備地址也一定3個u32表示,此處不能說是巧合,但是我們要清楚中斷產生設備的地址由幾個u32組成是由該設備所在總線決定的,對于pcie總線也確實是3,但是其他總線可能存在其他種的情況。現在我們來分析interrupt-map屬性,前三個數字是中斷設備地址,第四個數字是中斷設備的中斷源。因為interrupt-map-mask是全0,這樣不管與什么數字做與運算結果都是0,interrupt-map屬性的前4個數字也都是0,這說明在pcie@1,0下邊所有的中斷映射到中斷父節點的中斷都是一個中斷。接著是指向gic的,因為gic節點下#address-cells屬性為0,所以后邊不需要描述中斷父設備的地址了,后邊3個數字都是表示中斷父設備中斷源的。一句話描述就是pcie@1,0下的所有中斷都映射到gic,GIC_SPI類型的第29號中斷,觸發類型為高電平觸發。這個例子說明在中斷樹的最下邊可以是interrupt nexus節點。
以上例子中斷樹的根是gic,gic下邊有兩個孩子,一個是中斷設備timer@c600,一個是interrupt nexus節點pcie@1,0。gic直接管轄的interrupt domain用3個u32表示中斷源,timer@c600在這個interrupt domain下。pcie@1,0下定義了一個新的interrupt domain,在該interrupt domain下,中斷源用1個u32表示,pcie@1,0用interrupt-map和interrupt-map-mask屬性將下邊所有設備的中斷映射到一個gic下邊的中斷上。
五、根節點
一個最簡單的設備樹必須包含根節點,cpus節點,memory節點。根節點的名字及全路徑都是“/”,至少需要包含model和compatible兩個屬性。model屬性我們在屬性那節已經說過是用來描述產品型號的,類型為字符串,推薦的格式為“manufacturer,model-number”(非強制的)。根節點的model屬性描述的是板子的型號或者芯片平臺的型號,如:
model = “Atmel AT91SAM9G20 family SoC”
model = “Samsung SMDK5420 board based on EXYNOS5420”
從軟件的層面講model屬性僅僅表示一個名字而已,沒有更多的作用。compatible屬性則不同,該屬性決定軟件如何匹配硬件對硬件進行初始化。屬性那一節我們說過compatible屬性的類型是字符串數組,按照范圍從小到大的順序排列,每個字符串表示一種匹配類型。根節點的compatible屬性表示平臺如何匹配,比如‘compatible = “samsung,smdk5420”, “samsung,exynos5420”, “samsung,exynos5”’,表示軟件應該首先匹配’samsung,smdk5420’,這個是一款開發板。如果無法匹配,再試著匹配"samsung,exynos5420",這個是一款芯片平臺。如果還是無法匹配,還可以試著匹配 “samsung,exynos5”,這是一個系列的芯片平臺。這里說的匹配是指軟件根據該信息找到對應的代碼,如對應的初始化函數。
根節點表示的是整個板子或者芯片平臺,所以在系統初始化比較早的時候就需要確認是什么平臺,怎樣初始化。對于Linux,是通過在start_kernel函數調用setup_arch函數實現的。不同的架構,setup_arch函數的實現不同,對于arm架構,setup_arch函數源代碼位于arch/arm/kernel/setup.c中。
六、memory&chosen節點
簡單的設備樹也必須包含cpus節點和memory節點。memory節點用來描述硬件內存布局的。如果有多塊內存,既可以通過多個memory節點表示,也可以通過一個memory節點的reg屬性的多個元素支持。舉一個例子,假如某個64位的系統有兩塊內存,分別是
? RAM: 起始地址 0x0, 長度 0x80000000 (2GB)
? RAM: 起始地址 0x100000000, 長度 0x100000000 (4GB)
對于64位的系統,根節點的#address-cells屬性和#size-cells屬性都設置成2。一個memory節點的形式如下(還記得前幾節說過節點地址必須和reg屬性第一個地址相同的事情吧):
memory@0 {
device_type = “memory”;
reg = <0x000000000 0x00000000 0x00000000 0x80000000
0x000000001 0x00000000 0x00000001 0x00000000>;
};
兩個memory節點的形式如下:
memory@0 {
device_type = “memory”;
reg = <0x000000000 0x00000000 0x00000000 0x80000000>;
};
memory@100000000 {
device_type = “memory”;
reg = <0x000000001 0x00000000 0x00000001 0x00000000>;
};
chosen節點也位于根節點下,該節點用來給內核傳遞參數(不代表實際硬件)。對于Linux內核,該節點下最有用的屬性是bootargs,該屬性的類型是字符串,用來向Linux內核傳遞cmdline。規范中還定義了stdout-path和stdin-path兩個可選的、字符串類型的屬性,這兩個屬性的目的是用來指定標準輸入輸出設備的,在linux中,這兩個屬性基本不用。
memory和chosen節點在內核初始化的代碼都位于start_kernel()->setup_arch()->setup_machine_fdt()->early_init_dt_scan_nodes()函數中(位于drivers/of/fdt.c)
總結
- 上一篇: 机器学习面试题(转)
- 下一篇: iOS APP 反编译