設備樹DTS 學習:2-設備樹語法


背景

通過上一講了解完設備樹DTS有關概念,我們這一講就來基於設備樹例程,學習設備樹的語法規則。

參考:設備樹詳解dts設備樹語法詳解設備樹使用總結

設備樹框架

1個dts文件 + n個dtsi文件,它們編譯而成的dtb文件就是真正的設備樹。

基於同樣的軟件分層設計的思想,由於一個SoC可能對應多個machine,如果每個machine的設備樹都寫成一個完全獨立的.dts文件,那么勢必相當一些.dts文件有重復的部分。
為了解決這個問題,Linux設備樹目錄把一個SoC公用的部分或者多個machine共同的部分提煉為相應的.dtsi文件。
這樣每個.dts就只有自己差異的部分,公有的部分只需要"include"相應的.dtsi文件, 以保證整個設備樹的管理更加有序。
以solidrun公司的hummingboard為例,其組成為

imx6dl-hummingboard.dts
        |_imx6dl.dtsi
        |   |_imx6qdl.dtsi
        |_imx6qdl-microsom.dtsi
        |_imx6qdl-microsom-ar8035.dtsi

此外,dts/dtsi兼容c語言的一些語法,能使用宏定義,也能包含.h文件

設備樹用樹狀結構描述設備信息,它有以下幾種特性:

  1. 每個設備樹文件都有一個根節點,每個設備都是一個節點。
  2. 節點由 節點名 + 屬性 組成。
  3. 節點間可以嵌套,形成父子關系,這樣就可以方便的描述設備間的關系。
  4. 每個設備的屬性都用一組key-value對(鍵值對)來描述。
  5. 每個屬性的描述用;結束

所以,一個設備樹的基本框架可以寫成下面這個樣子,一般來說,/表示板子,它的子節點node1表示SoC上的某個控制器,控制器中的子節點node2表示掛接在這個控制器上的設備(們)

/ {                                 //根節點
    node1{                          //node1是節點名,是/的子節點
        key=value;                  //node1的屬性
        ...
        node2{                      //node2是node1的子節點
            key=value;              //node2的屬性
            ...
        }
    }                               //node1的描述到此為止
    node3{
        key=value;
        ...
    }
}

以下是一顆最簡單的設備樹:
注意/dts-v1/;是必須的,有時候正是因為忽略了它而引起了syntax error且沒有其他提示。

/dts-v1/;
/ {
 
};

節點node

{}包圍起來的結構稱之為節點,dts中最開頭的/ {},稱為根節點。
在節點中,以 key = value 代表節點屬性。
樹中每個表示一個設備的節點都需要一個 compatible 屬性。

節點名 name

  • 節點名稱:每個節點名格式為:<name>[@<unit_address>],其中:
    • :設備名,就是一個不超過31位的簡單 ascii 字符串,節點的命名應該根據它所體現的是什么樣的設備。
    • <unit_address> :設備地址,用來唯一標識一共節點。沒有指定<unit_address>時,同級節點命名必須是唯一的;但只要<unit_address>不同,多個節點也可以使用一樣的通用名稱。

下面是典型節點名的寫法:

/ {
        model = "Freescale i.MX23 Evaluation Kit";
        compatible = "fsl,imx23-evk", "fsl,imx23";

        memory {
                reg = <0x40000000 0x08000000>;
        };
        // 注意這里
        apb@80000000 {
                ...
        };
}

上面的節點名是apb,節點路徑是/apb@80000000 ,這點要注意,因為根據節點名查找節點的API的參數是不能有"@xxx"這部分的。

Linux中的設備樹還包括幾個特殊的節點:比如chosen,chosen節點不描述一個真實設備,而是用於firmware傳遞一些數據給OS,比如bootloader傳遞內核啟動參數給內核

/include/ "zynq-7000.dtsi"

/ {
        model = "Zynq ZC702 Development Board";
        compatible = "xlnx,zynq-zc702", "xlnx,zynq-7000";

        ...

        chosen {
                bootargs = "console=ttyPS1,115200 earlyprintk";
        };
};

引用

當我們找一個節點的時候,我們必須書寫完整的節點路徑,這樣當一個節點嵌套比較深的時候就不是很方便。所以,設備樹允許我們用下面的形式為節點標注引用(起別名),借以省去冗長的路徑。
標號引用常常還作為節點的重寫方式,用於修改節點屬性。

  • 格式:
    • 聲明別名: 別名 : 節點名
    • 訪問 : &別名

編譯設備樹的時候,相同的節點的不同屬性信息都會被合並,相同節點的相同的屬性會被重寫(覆蓋前值),使用引用可以避免移植者四處找節點,直接在板級.dts增改即可。

/include/ "imx53.dtsi"

/ {
        model = "Freescale i.MX53 Automotive Reference Design Board";
        compatible = "fsl,imx53-ard", "fsl,imx53";

        memory {
                reg = <0x70000000 0x40000000>;
        };

        eim-cs1@f4000000 {
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "fsl,eim-bus", "simple-bus";
                reg = <0xf4000000 0x3ff0000>;

                lan9220@f4000000 {
                        compatible = "smsc,lan9220", "smsc,lan9115";
                        reg = <0xf4000000 0x2000000>;
                        phy-mode = "mii";
                        interrupt-parent = <&gpio2>; // 直接使用引用

                        vdd33a-supply = <&reg_3p3v>;
                };
        };

        regulators {
                compatible = "simple-bus";

                reg_3p3v: 3p3v {                     // 定義一個引用
                        compatible = "regulator-fixed";
                        regulator-name = "3P3V";
                };
        };

        ...
        // 引用一個節點,新增/修改其屬性。
        &reg_3p3v {
            regulator-always-on;
        }

節點屬性 property

屬性一般由 key = value; 鍵值對構成。
Linux設備樹語法中定義了一些具有規范意義的屬性,包括:compatible, address, interrupt等,這些信息能夠在內核初始化找到節點的時候,自動解析生成相應的設備信息。
此外,還有一些Linux內核定義好的,一類設備通用的有默認意義的屬性,這些屬性一般不能被內核自動解析生成相應的設備信息,但是內核已經編寫的相應的解析提取函數,常見的有 "mac_addr","gpio","clock","power"。"regulator" 等等。

  • 簡單的鍵-值對,它的值可以為空或者包含一個任意字節流。雖然數據類型並沒有編碼進數據結構,但在設備樹源文件中任有幾個基本的數據表示形式:
    • 文本字符串(無結束符)可以用雙引號表示: string-property = "a string"
    • Cells是 32 位無符號整數,用尖括號限定: cell-property = <0xbeef 123 0xabcd1234>
    • 二進制數據用方括號限定: binary-property = [01 23 45 67];
    • 不同表示形式的數據可以使用逗號連在一起: mixed-property = "a string", [01 23 45 67], <0x12345678>;
    • 逗號也可用於創建字符串列表: string-list = "red fish", "blue fish";
    • 混合形式:上述幾種的混合形式

compatible 兼容性

如果一個節點是設備節點,那么它一定要有compatible(兼容性),因為這將作為驅動和設備(設備節點)的匹配依據,compatible(兼容性)的值可以有不止一個字符串以滿足不同的需求。(設備節點中對應的節點信息已經被內核構造成struct platform_device。驅動可以通過相應的函數從中提取信息。)
compatible屬性是用來查找節點的方法之一,另外還可以通過節點名或節點路徑查找指定節點。
而根節點的compatible也是非常重要的,一般在系統啟動以后,用於識別對應系統一些東西,並由此進行對應的初始化。

  • 格式:compatible = "<manufacturer>,<model>" [, "<manufacturer>,<model>"]
    • manufacturer指定廠家名,model指定特定設備型號;后續的<manufacturer,model>指定兼容的設備型號(其中,后續的<manufacturer> 可空,第二個model也可空)。
      我們來看 compatible 是如何與 驅動捆綁在一起的:
      可以看出,驅動中用於匹配的結構使用的compatible和設備樹中一模一樣,否則就可能無法匹配,這里另外的一點是struct of_device_id數組的最后一個成員一定是空,因為相關的操作API會讀取這個數組直到遇到一個空。

1)先隨便在設備樹中出一個網卡設備,關鍵是找到 compatible 屬性中的 <model>值。

// 文件節選於:arch/arm/boot/dts/vexpress-v2m-rs1.dtsi
ethernet@2,02000000 {
    compatible = "smsc,lan9118", "smsc,lan9115";
    reg = <2 0x02000000 0x10000>;
    interrupts = <15>;
    phy-mode = "mii";
    reg-io-width = <4>;
    smsc,irq-active-high;
    smsc,irq-push-pull;
    vdd33a-supply = <&v2m_fixed_3v3>;
    vddvario-supply = <&v2m_fixed_3v3>;
};

2)在驅動中(為了方便讀者理解,這里在內核源碼根目錄下查找,實際上就是在driver目錄中),找到對應的.compatible 關鍵字所在的文件以及行數。

$ find . 2>/dev/null | grep lan9115
arch/arm/boot/dts/vexpress-v2m-rs1.dtsi:50:     compatible = "smsc,lan9118", "smsc,lan9115";
arch/arm/boot/dts/vexpress-v2m.dtsi:49:         compatible = "smsc,lan9118", "smsc,lan9115";
drivers/net/ethernet/smsc/smsc911x.c:2578:      { .compatible = "smsc,lan9115", },

3)順藤摸瓜,找到所在行,也就找到了用來描述設備信息的結構體of_device_id

// 節選於 drivers/net/ethernet/smsc/smsc911x.c
#ifdef CONFIG_OF
static const struct of_device_id smsc911x_dt_ids[] = {
    { .compatible = "smsc,lan9115", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, smsc911x_dt_ids);
#endif

可以看出,驅動中用於匹配的結構使用的compatible和設備樹中一模一樣,且,字符串需要嚴格匹配。

注:這里另外的一點是 struct of_device_id 數組的最后一個成員一定是空,因為相關的操作API會讀取這個數組直到遇到一個空。
i2c和spi驅動還支持一種“別名匹配”的機制,就以pcf8523為例,假設某程序員在設備樹中的pcf8523設備節點中寫了compatible = "pcf8523";,顯然相對於驅動id_table中的"nxp,pcf8523",他遺漏了nxp字段,但是驅動卻仍然可以匹配上,因為別名匹配對compatible中字符串里第二個字段敏感。
驅動程序將直接和設備樹里的設備節點進行配對,是通過設備節點中的compatible(兼容性)來與設備節點進行配對的,具體的應用詳見 基於i2c子系統的驅動分析基於platform總線的驅動分析

address 地址屬性

有關節點的地址,比如i2c@021a0000,雖然它在名字后面跟了地址,但是正式的設置是在reg屬性中設置。
(幾乎)所有的設備都需要與CPU的IO口相連,所以其IO端口信息就需要在設備節點節點中說明。常用的屬性有:

  • #address-cells = <CNT>,用來描述子節點"reg"屬性的地址表中用來描述首地址的cell的數量
  • #size-cells = <CNT>, 用來描述子節點"reg"屬性的地址表中用來描述地址長度的cell的數量。
  • reg = <address ... length>: address 代表基地址, length 代表長度。基址和長度的格式是可變的,addr由父節點的#address-cells個uint32值組成,len由父節點的#size-cells個uint32值組成。表明了設備使用的一個地址范圍。
    例如:
	aips-bus@02000000 { /* AIPS1 */
	    compatible = "fsl,aips-bus", "simple-bus";
	    #address-cells = <1>;
	    #size-cells = <1>;
	    reg = <0x02000000 0x100000>;

	    i2c1: i2c@021a0000 {
	        #address-cells = <1>;
	        #size-cells = <0>;
	        compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
	        reg = <0x021a0000 0x4000>;

			rtc: rtc@68 {
			    compatible = "stm,mt41t62";
			    reg = <0x68>;
			};
	    };
	};

/*
我們知道,aips-bus@02000000 是 i2c@021a0000 的父節點;i2c@021a0000 是 rtc@68 的父節點。

aips-bus@02000000的 #address-cells 和#size-cells均為1,所以 i2c@021a0000 中的 `reg` 格式為: `<address length>`
i2c@021a0000的 #address-cells 和#size-cells分別為1和0,所以 rtc@68 中的 `reg` 格式為: `<address>`

通俗來講,如果現在有 一個節點A的 #address-cells 和#size-cells分別為2和1;那么A的子節點B 的 `reg`格式為 `<address address length>`
*/

interrupts 中斷屬性

中斷產生設備用interrupts屬性描述中斷源(interrupt specifier),因為不同的硬件描述中斷源需要的數據量不同,所以interrupts屬性的類型也是 。為了明確表示一個中斷由幾個u32表示,又引入了#interrupt-cells屬性,#interrupt-cells屬性的類型是u32,假如一個中斷源需要2個u32表示(一個表示中斷號,另一個表示中斷類型),那么#interrupt-cells就設置成2。
有些情況下,設備樹的父節點不是中斷的父節點(主要是中斷控制器一般不是父節點),為此引入了interrupt-parent屬性,該屬性的類型是 ,用來引用中斷父節點(我們前邊說過,一般用父節點的標簽,這個地方說中斷父節點而不是中斷控制器是有原因的)。如果設備樹的父節點就是中斷父節點,那么可以不用設置interrupt-parent屬性。interrupts屬性和interrupt-parent屬性都是中斷產生設備節點的屬性,但是#interrupt-cells屬性不是,#interrupt-cells屬性是中斷控制器節點以及interrupt nexus節點的屬性,這兩類節點都可能是中斷父節點。

一個計算機系統中大量設備都是通過中斷請求CPU服務的,所以設備節點中就需要在指定中斷號。常用的屬性有:

  • interrupt-controller: 一個空屬性用來聲明這個node接收中斷信號,即這個node是一個中斷控制器。
  • #interrupt-cells :是中斷控制器節點的屬性,用來標識這個控制器需要幾個單位做中斷描述符,用來描述子節點中interrupts屬性使用了父節點中的interrupts屬性的具體的哪個值。一般,如果父節點的該屬性的值是3,則子節點的interrupts一個cell的三個32bits整數值分別為:<中斷域 中斷 觸發方式>,如果父節點的該屬性是2,則是<中斷 觸發方式>
  • interrupt-parent:標識此設備節點屬於哪一個中斷控制器,如果沒有設置這個屬性,會自動依附父節點的
  • interrupts:一個中斷標識符列表,表示每一個中斷輸出信號。
  • reg : 在schips todo

這里重點說明一下,interrupts 屬性,在ARM GIC(Generic Interrupt Controller)中:

備注:ARM GIC 說明文檔位於:Documentation/devicetree/bindings/arm/gic.txt ;此外,本人並沒有找到 #interrupt-cells為1個時的文檔說明。

interrupt-cells為3時,interrupts包含三個cells,如interrupts = <0 168 4>

第一個cell代表中斷類型:0 表示SPI中斷,1 表示PPI中斷。

第二個cell代表具體的中斷類型:、

  • PPI中斷:私有外設中斷(Private Peripheral Interrupt),是每個CPU私有的中斷。最多支持16個PPI中斷,范圍【0 - 15】。
  • SPI中斷類型:公用外設中斷(Shared Peripheral Interrupt),最多可以支持988個外設中斷,范圍【0 - 987】。

第三個cell代表中斷觸發標志:

  • bits [ 3 :0 ] 觸發類型和級別標志:
    1 = 低- 至- 高邊沿觸發
    2 = 高- 到- 低邊沿觸發
    4 = 活躍的高水平 - 敏感
    8 = 低電平有效 - 敏感
  • bits [ 15 :8 ] PPI中斷cpu掩碼。每個位對應於每個位附加到GIC的8個可能的cpu。指示設置為"1"的位中斷被連接到該CPU 。只有有效的PPI中斷。

interrupt-cells為2時,interrupts包含2個cells,如interrupts = <2 4>

第一個cell代表具體的中斷類型:

  • SGI中斷:軟件觸發中斷(Software Generated Interrupt),通常用於多核間通訊,最多支持16個SGI中斷,硬件中斷號從ID0~ID15。
  • PPI中斷:私有外設中斷(Private Peripheral Interrupt),是每個CPU私有的中斷。最多支持16個PPI中斷,硬件中斷號從ID16~ID31。
  • SPI中斷類型:公用外設中斷(Shared Peripheral Interrupt),最多可以支持988個外設中斷,硬件中斷號從ID32~ID1019。

第二個cell代表中斷觸發標志:

bits [ 3 :0 ] 觸發類型和級別標志:

  • 1 = 低- 至- 高邊沿觸發

  • 2 = 高- 到- 低邊沿觸發

  • 4 = 活躍的高水平- 敏感

  • 8 = 低電平有效- 敏感

    bits [ 15 :8 ] PPI中斷cpu掩碼。每個位對應於每個位附加到GIC的8個可能的cpu。指示設置為"1"的位中斷被連接到該CPU 。只有有效的PPI中斷。

/ {
    compatible = "acme,coyotes-revenge";
    #address-cells = <1>;
    #size-cells = <1>;
    interrupt-parent = <&intc>;//指定依附的中斷控制器是intc

    serial@101f0000 {   //子節點:串口設備
        compatible = "arm,pl011";
        reg = <0x101f0000 0x1000 >;
        interrupts = < 1 0 >;
    };

    intc: interrupt-controller@10140000 { //intc中斷控制器
        compatible = "arm,pl190";
        reg = <0x10140000 0x1000 >;
        interrupt-controller;//定義為中斷控制器設備
        #interrupt-cells = <2>;
    };
}

GPIO 屬性

gpio也是最常見的IO口,常用的屬性有:

  • "gpio-controller",用來說明該節點描述的是一個gpio控制器
  • "#gpio-cells",用來描述gpio使用節點的屬性一個cell的內容,即 `屬性 = <&引用GPIO節點別名 GPIO標號 工作模式>

通過上面的屬性定義以后,就可以使用它,例如:

  2 &spi_1 {
  1     status = "okay";
388     cs-gpios = <&gpa2 5 GPIO_ACTIVE_HIGH>; // 使用 GPIO A2 第5個引腳,
  1
  2     w25q80bw@0 {
  3         #address-cells = <1>;
  4         #size-cells = <1>;
  5         compatible = "w25x80";
  6         reg = <0>;
  7         spi-max-frequency = <1000000>;
  8
  9         controller-data {
 10             samsung,spi-feedback-delay = <0>;
 11         };
 12

驅動自定義key屬性

針對具體的設備,有部分屬性很難做到通用,需要驅動自己定義好。
可以在設備樹中自定義key屬性,再在驅動中通過內核的屬性提取解析函數進行值的獲取。
比如:

/* 有關的 設備樹寫法 */
  6         ethernet@2,02000000 {
  5             compatible = "smsc,lan9118", "smsc,lan9115";
  4             reg = <2 0x02000000 0x10000>;
  3             interrupts = <15>;
  2             phy-mode = "mii";
  1             reg-io-width = <4>;
55              smsc,irq-active-high;   // 自定義key
  1             smsc,irq-push-pull;     // 自定義key
  2             vdd33a-supply = <&v2m_fixed_3v3>;
  3             vddvario-supply = <&v2m_fixed_3v3>;
  4         };
  5
  6         usb@2,03000000 {
  7             compatible = "nxp,usb-isp1761";
  8             reg = <2 0x03000000 0x20000>;
  9             interrupts = <16>;
arch/arm/boot/dts/vexpress-v2m-rs1.dtsi 

/* 有關的驅動寫法 */

2389     if (of_get_property(np, "smsc,irq-active-high", NULL))
   1         config->irq_polarity = SMSC911X_IRQ_POLARITY_ACTIVE_HIGH;
   2
   3     if (of_get_property(np, "smsc,irq-push-pull", NULL))
   4         config->irq_type = SMSC911X_IRQ_TYPE_PUSH_PULL;
   5
   6     if (of_get_property(np, "smsc,force-internal-phy", NULL))
   7         config->flags |= SMSC911X_FORCE_INTERNAL_PHY;
   8
   9     if (of_get_property(np, "smsc,force-external-phy", NULL))
  10         config->flags |= SMSC911X_FORCE_EXTERNAL_PHY;
  11
  12     if (of_get_property(np, "smsc,save-mac-address", NULL))
  13         config->flags |= SMSC911X_SAVE_MAC_ADDRESS;
  14
  15     return 0;
drivers/net/ethernet/smsc/smsc911x.c 

附錄:補充對於interrupt-parent的一些知識點

為什么會有interrupt-parent

首先講講Linux設備管理中對中斷的設計思路演變。隨着linux kernel的發展,在內核中將interrupt controller抽象成irqchip這個概念越來越流行,甚至GPIO controller也可以被看出一個interrupt controller chip,這樣,系統中至少有兩個中斷控制器了。另外,在硬件上,隨着系統復雜度加大,外設中斷數據增加,在這種趨勢下,內核中原本的中斷源直接到中斷號的方式已經很難繼續發展了,為了解決這些問題,linux kernel的大牛們就創造了irq domain(中斷域)這個概念。domain在內核中有很多,除了irqdomain,還有power domain,clock 這些domain等等;所謂domain,就是領域,范圍的意思(即:任何的定義出了這個范圍就沒有意義了)。

實際上系統可以需要多個中斷控制器進行級聯,形成事實上的硬件中斷處理結構:

img

如上所述,系統中所有的interrupt controller會形成樹狀結構,對於每個interrupt controller都可以連接若干個外設的中斷請求(interrupt source,中斷源),interrupt controller會對連接其上的interrupt source(根據其在Interrupt controller中物理特性)進行編號(也就是HW interrupt ID了)。有了irq domain這個概念之后,這個編號僅僅限制在本interrupt controller范圍內。

有了這樣的設計,CPU(Linux 內核)就可以根據級聯的規則一級一級的找到想要訪問的中斷。當然,通常我們關心的只是內核中的中斷號,具體這個中斷號是怎么找到相應的中斷源的,我們作為程序員往往不需要關心。

在寫設備樹的時候,設備樹就是要描述嵌入式軟件開發中涉及的所有硬件信息。所以,設備樹就需要准確描述硬件上處理中斷的這種樹狀結構,如此,就有了我們的interrupt-parant這樣的概念:用來連接這樣的樹狀結構的上下級,用於表示這個中斷歸屬於哪個interrupt controller,比如,一個接在GPIO上的按鍵,它的組織形式就是:

中斷源--interrupt parent-->GPIO--interrupt parent-->GIC1--interrupt parent-->GIC2--...-->CPU

有了parant,我們就可以使用一級一級的偏移量來最終獲得當前中斷的絕對編號。

可以看出,在我板子上的dm9000的的設備節點中,它的"interrupt-parent"引用了"exynos4x12-pinctrl.dtsi"(被板級設備樹的exynos4412.dtsi包含)中的gpx0節點:
img

而在gpx0節點中,指定了#interrupt-cells = <2>;,所以在dm9000中的屬性interrupts = <6 4>;表示dm9000的的中斷在作為irq parant的gpx0中的中斷偏移量,即gpx0中的屬性interrupts中的<0 22 0>,通過查閱exynos4412的手冊知道,對應的中斷號是EINT[6]。

img


免責聲明!

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



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