高通平臺Android源碼分析之Linux內核設備樹

轉載自 http://huaqianlee.me/2015/08/19/Android/高通平臺Android源碼分析之Linux內核設備樹-DT-Device-Tree-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鍵值對,如下:

1
/ {
    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(整數組成)、數組及二進制等任意字節流;

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

1
2
3
4
5
6
7
8
9
10
# 字符串,用雙引號引用: 
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的硬件配置如下:

  • 一個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,從屬設備:
      • Maxim DS1338時鐘芯片,從設備I2C地址 1101000(0x58)
    • 64MB Nor flash,基地址爲0x30000000

初始化結構

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

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

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

CPUs

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

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

    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請求之後再填寫相應內容。如下:

1
/ {
    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";
        };
    };
};

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

目前,這個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”。

設備尋址

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

1
2
3
4
5
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 ids關聯。如下:

1
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,並且取reg屬性的第一個address值。

有內存映像地址的設備

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

1
/ {
    #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 >;
    };

    ...

};

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

1
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>;
    };
};

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

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

無內存映像的設備

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

1
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>;
    };
};

地址轉換

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

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

而不是根節點的直接孩子的節點不使用cpu的地址域,爲了能將其映射到cpu的內存地址,設備樹就得對其地址進行轉換,ranges屬性就是用來實現這個目的的,加入ranges屬性後如下:

1
/ {
    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,但我覺得不對,所以自己做了修改,下同,就不再說明

// 相信大家直接通過這個列表就能知道地址怎麼轉換的了,如下:

1. 偏移量爲0的Chipselect0映射到0x10100000~0x1010ffff
2. 偏移量爲0的Chipselect1映射到0x10160000~0x1016ffff
3. 偏移量爲0的Chipselect2映射到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直接訪問。

中斷

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

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

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

1
/ {
    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含義在內核中的相關文檔有詳細描述,比如arm gic 中斷:

1
# 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.

設備特有數據

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

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

特殊節點

aliases節點

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

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

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

chosen節點

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

1
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包括:

1
2
3
4
5
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,如下:

1
2
3
4
# 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目錄,其下又分爲很多子目錄。

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”,在驅動中就有相應分支處理:

1
2
3
4
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”屬性:

1
2
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結點如下:

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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”字符串數組屬性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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的默認配置文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 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文件的編譯開關,如下:

1
2
3
4
5
# 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。關鍵源碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 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文件中定義平臺信息,如下:

1
# 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文件中重新定義了平臺信息,如下:

1
# 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

發佈了2 篇原創文章 · 獲贊 7 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章