裝置樹總結

誰動了我的奶油發表於2020-10-06

參考:
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’屬性確定。

dma-ranges屬性的結構和定義與ranges屬性完全相同,唯一不同的是地址是dma使用的地址,ranges中的地址是cpu使用的地址。

有的時候在一個節點中需要引用另外一個節點,比如某個外設的中斷連在哪個中斷控制器上。在講節點那一節我們說過,可以通過節點的全路徑指定是哪個節點,但這種方法非常繁瑣。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)

相關文章