- 0、設備樹是什么?
- 1、DTS
- 1.1 dts簡介
- 1.2 dts例子
- 2、DTC(Device Tree Compiler)
- 3、DTB(Device Tree Blob)
- 4、綁定(Binding)
- 5、Bootloader
-
- compatible屬性
- 7、 #address-cells和#size-cells屬性
- 8、reg屬性
- 9、根節點兼容性
- 10、地址編碼
- 11、ranges
- 12、中斷連接
- 13、GPIO
- 14、時鐘
- 15、pinmux
- 16、設備點屬性回顧
- 17、設備添加label
- 1.尋找節點
- 2.讀取屬性
- 3.內存映射
- 4.解析中斷
- 5.獲取與節點對應的platform_device
-
- 打個總結
- 1、注冊platform_device,綁定resource,即內存、IRQ等板級信息
- 2、注冊i2c_board_info,指定IRQ等板級信息
- 3、注冊spi_board_info,指定IRQ等板級信息
- 4、多個針對不同電路板的設備,以及相關的回調函數
- 5、設備與驅動的匹配方式
- 6、設備的平臺數據屬性化
- 18、站在Android看設備樹
- 1、概覽
- 2、加載設備樹
- 3、術語
- 4、分割 DT
- 5、構建主 DT 和疊加 DT
- 6、對 DT 進行分區
- 7、在引導加載程序中運行
- 8、保持兼容性
- 9、確保安全
- 10、小結-白話一刻
看完這個文章,搞清楚下面三點就OK:
- 1、設備樹是什么?
- 2、設備樹有啥用?
- 3、設備樹怎么用?
至于那些來歷啊、傳聞啊,確實很有意思,但是有很多的博客和書有寫,我就不重復那個部分,來強調一下知識。
先整個圖片:
0、設備樹是什么?
我的理解,就是以前對于很多的冗余重復的東西進行來組件化。
以前每個硬件都有自己的代碼進行細節描述,但是將這些代碼組件化后,你每個板子需要用什么,給你個dts樹架子,你需要啥,就選啥,少啥就加啥。
就別重復整了。
同時講內核和dts組合,那些細節代碼哪兒涼快哪里待著,因為我內核是需要接觸硬件的一些東西(應用包含了驅動),所以需要板子的細節,但是現在有了dts,那么那些細節代碼byebye。
沒dts之前–>把板子的信息放在內核的
有dts之后–>dts將硬件細節傳輸給內核,內核干凈了
在設備樹中,可描述的信息包括(原先這些信息大多被硬編碼在內核中):
·CPU的數量和類別。·內存基地址和大小。·總線和橋。·外設連接。·中斷控制器和中斷使用情況。·GPIO控制器和GPIO使用情況。·時鐘控制器和時鐘使用情況。
這些信息都是一些硬件的信息,我最近要解析的就是內存地址,可惜我現在還無從下手。
它基本上就是畫一棵電路板上CPU、總線、設備組成的樹,Bootloader會將這棵樹傳遞給內核,然后內核可以識別這棵樹,并根據它展開出Linux內核中的platform_device、i2c_client、spi_device等設備,而這些設備用到的內存、IRQ等資源,也被傳遞給了內核,內核會將這些資源綁定給展開的相應的設備。
這里啰嗦幾點:
- 1、dts是描述設備信息(硬件板子)的文本
- 2、dts不是直接用的,而是編譯后給內核用的
- 3、dts不是直接和內核聯系,而是通過bootloader將編譯后的文件傳輸給kernel
下面來看看什么是DTS?
1、DTS
文件.dts是一種ASCII文本格式的設備樹描述,此文本格式非常人性化,適合人類的閱讀習慣。但是內核肯定和人不一樣,所以就的轉換。
基本上,在ARM Linux中,一個.dts文件對應一個ARM的設備,一般放置在內核的arch/arm/boot/dts/目錄中。值得注意的是,在arch/powerpc/boot/dts、arch/powerpc/boot/dts、arch/c6x/boot/dts、arch/openrisc/boot/dts等目錄中,也存在大量的.dts文件,這證明DTS絕對不是ARM的專利。
1.1 dts簡介
由于一個SoC可能對應多個設備(一個SoC可以對應多個產品和電路板),這些.dts文件勢必須包含許多共同的部分,Linux內核為了簡化,**把SoC公用的部分或者多個設備共同的部分一般提煉為.dtsi,**類似于C語言的頭文件。(我要解析的就是.dtsi文件)
其他的設備對應的.dts就包括這個.dtsi。譬如,對于VEXPRESS而言,vexpress-v2m.dtsi就被vexpress-v2p-ca9.dts所引用,vexpress-v2p-ca9.dts有如下一行代碼:
/include/ "vexpress-v2m.dtsi"
這個和我寫bp那里有點相似的思維,果然include包含世界。
當然,和C語言的頭文件類似,.dtsi也可以包括其他的.dtsi,譬如幾乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi。
文件.dts(或者其包括的.dtsi)的基本元素即為前文所述的節點和屬性,代碼清單18.1給出了一個設備樹結構的模版。
1 / {2 node1 {3 a-string-property = "A string";4 a-string-list-property = "first string", "second string";5 a-byte-data-property = [0x01 0x23 0x34 0x56];6 child-node1 {7 first-child-property;8 second-child-property = <1>;9 a-string-property = "Hello, world";
10 };
11 child-node2 {
12 };
13 };
14 node2 {
15 an-empty-property;
16 a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
17 child-node1 {
18 };
19 };
20 };
上述.dts文件并沒有什么真實的用途,但它基本表征了一個設備樹源文件的結構:
1個root節點"/";root節點下面含一系列子節點,本例中為node1和node2;節點node1下又含有一系列子節點,本例中為child-node1和child-node2;
各節點都有一系列屬性。
這些屬性可能為空,如an-empty-property;可能為字符串,如a-string-property;可能為字符串數組,如a-string-list-property;可能為Cells(由u32整數組成),如second-child-property;可能為二進制數,如a-byte-data-property。
1.2 dts例子
上面知道了基礎的知識,下來整個栗瞅瞅
先來文字描述:
1個雙核ARM Cortex-A932位處理器;
ARM本地總線上的內存映射區域分布有兩個串口(分別位于0x101F1000和0x101F2000)、GPIO控制器(位于0x101F3000)、SPI控制器(位于0x10170000)、中斷控制器(位于0x10140000)和一個外部總線橋;
外部總線橋上又連接了SMC SMC91111以太網(位于0x10100000)、I2C控制器(位于0x10160000)、64MB NOR Flash(位于0x30000000);
外部總線橋上連接的I2C控制器所對應的I2C總線上又連接了Maxim DS1338實時鐘(I2C地址為0x58)。
轉換成dts文件
1 / {2 compatible = "acme,coyotes-revenge";3 #address-cells = <1>;4 #size-cells = <1>;5 interrupt-parent = <&intc>;67 cpus {8 #address-cells = <1>;9 #size-cells = <0>;
10 cpu@0 {
11 compatible = "arm,cortex-a9";
12 reg = <0>;
13 };
14 cpu@1 {
15 compatible = "arm,cortex-a9";
16 reg = <1>;
17 };
18 };
19
20 serial@101f0000 {
21 compatible = "arm,pl011";
22 reg = <0x101f0000 0x1000 >;
23 interrupts = < 1 0 >;
24 };
25
26 serial@101f2000 {
27 compatible = "arm,pl011";
28 reg = <0x101f2000 0x1000 >;
29 interrupts = < 2 0 >;
30 };
31
32 gpio@101f3000 {
33 compatible = "arm,pl061";
34 reg = <0x101f3000 0x1000
35 0x101f4000 0x0010>;
36 interrupts = < 3 0 >;
37 };
38
39 intc: interrupt-controller@10140000 {
40 compatible = "arm,pl190";
41 reg = <0x10140000 0x1000 >;
42 interrupt-controller;
43 #interrupt-cells = <2>;
44 };
45
46 spi@10115000 {
47 compatible = "arm,pl022";
48 reg = <0x10115000 0x1000 >;
49 interrupts = < 4 0 >;
50 };
51
52 external-bus {
53 #address-cells = <2>
54 #size-cells = <1>;
55 ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
56 1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
57 2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
58
59 ethernet@0,0 {
60 compatible = "smc,smc91c111";
61 reg = <0 0 0x1000>;
62 interrupts = < 5 2 >;
63 };
64
65 i2c@1,0 {
66 compatible = "acme,a1234-i2c-bus";
67 #address-cells = <1>;
68 #size-cells = <0>;
69 reg = <1 0 0x1000>;
70 interrupts = < 6 2 >;
71 rtc@58 {
72 compatible = "maxim,ds1338";
73 reg = <58>;
74 interrupts = < 7 3 >;
75 };
76 };
77
78 flash@2,0 {
79 compatible = "samsung,k8f1315ebm", "cfi-flash";
80 reg = <2 0 0x4000000>;
81 };
82 };
83 };
在上述.dts文件中,可以看出external-bus是根節點的子節點,而I2C又是external-bus的子節點,RTC又進一步是I2C的子節點。每一級節點都有一些屬性信息。
下面再來看看什么是DTC
2、DTC(Device Tree Compiler)
DTC是將.dts編譯為.dtb的工具。
DTC的源代碼位于內核的scripts/dtc目錄中,在Linux內核使能了設備樹的情況下,編譯內核的時候主機工具DTC會被編譯出來,對應于scripts/dtc/Makefile中“hostprogs-y:=dtc”這一hostprogs的編譯目標。
當然,DTC也可以在Ubuntu中單獨安裝,命令如下:
sudo apt-get install device-tree-compiler
在Linux內核的arch/arm/boot/dts/Makefile中,描述了當某種SoC被選中后,哪些.dtb文件會被編譯出來,如與VEXPRESS對應的.dtb包括:(哪兒都跑不掉的makefile)
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下,我們可以單獨編譯設備樹文件。當我們在Linux內核下運行make dtbs時,若我們之前選擇了ARCH_VEXPRESS,上述.dtb都會由對應的.dts編譯出來,因為arch/arm/Makefile中含有一個.dtbs編譯目標項目。
DTC除了可以編譯.dts文件以外,其實也可以“反匯編”.dtb文件為.dts文件,其指令格式為:
./scripts/dtc/dtc -I dtb -O dts -o xxx.dts arch/arm/boot/dts/xxx.dtb
3、DTB(Device Tree Blob)
文件.dtb是.dts被DTC編譯后的二進制格式的設備樹描述,可由Linux內核解析,當然U-Boot這樣的bootloader也是可以識別.dtb的。(至于誰用,得看啟動流程,或者都用)
通常在我們為電路板制作NAND、SD啟動映像時,會為.dtb文件單獨留下一個很小的區域以存放之,之后bootloader在引導內核的過程中,會先讀取該.dtb到內存。
Linux內核也支持一種變通的模式,可以不把.dtb文件單獨存放,而是直接和zImage綁定在一起做成一個映像文件,類似cat zImage xxx.dtb>zImage_with_dtb的效果。當然內核編譯時候要使能CONFIG_ARM_APPENDED_DTB這個選項,以支持“Use appended device tree blob to zImage”(見Linux內核中的菜單)。(單獨整個鏡像)
4、綁定(Binding)
對于設備樹中的節點和屬性具體是如何來描述設備的硬件細節的,一般需要文檔來進行講解,文檔的后綴名一般為.txt。
在這個.txt文件中,需要描述對應節點的兼容性、必需的屬性和可選的屬性。
這些文檔位于內核的Documentation/devicetree/bindings目錄下,其下又分為很多子目錄。譬如,Documentation/devicetree/bindings/i2c/i2c-xiic.txt描述了Xilinx的I2C控制器,其內容如下:
Xilinx IIC controller:
Required properties:
- compatible : Must be "xlnx,xps-iic-2.00.a"
- reg : IIC register location and length
- interrupts : IIC controller unterrupt
- #address-cells = <1>
- #size-cells = <0>
Optional properties:
- Child nodes conforming to i2c bus binding
Example:axi_iic_0: i2c@40800000 {
compatible = "xlnx,xps-iic-2.00.a";
interrupts = < 1 2 >;
reg = < 0x40800000 0x10000 >;#size-cells = <0>;#address-cells = <1>;};
(我想解析,第一步這里知道了寄存器的含義,下一步找到我要解析的寄存器,學習怎么去解析讀取就好誒)
基本可以看出,設備樹綁定文檔的主要內容包括:
- ·關于該模塊最基本的描述。
- ·必需屬性(Required Properties)的描述。
- ·可選屬性(Optional Properties)的描述。
- ·一個實例。
Linux內核下的scripts/checkpatch.pl會運行一個檢查,如果有人在設備樹中新添加了compatible字符串,而沒有添加相應的文檔進行解釋,checkpatch程序會報出警告:UNDOCUMENTED_DT_STRINGDT compatible string xxx appears un-documented,因此程序員要養成及時寫DT Binding文檔的習慣。
5、Bootloader
Uboot設備從v1.1.3開始支持設備樹,其對ARM的支持則是和ARM內核支持設備樹同期完成。為了使能設備樹,需要在編譯Uboot的時候在config文件中加入:
#define CONfiG_OF_LIBFDT
在Uboot中,可以從NAND、SD或者TFTP等任意介質中將.dtb讀入內存,假設.dtb放入的內存地址為0x71000000,之后可在Uboot中運行fdt addr命令設置.dtb的地址,如:
UBoot> fdt addr 0x71000000
fdt的其他命令就變得可以使用,如fdt resize、fdt print等。
對于ARM來講,可以通過bootz kernel_addr initrd_address dtb_address的命令來啟動內核,即dtb_address作為bootz或者bootm的最后一次參數,第一個參數為內核映像的地址,第二個參數為initrd的地址,若不存在initrd,可以用“-”符號代替。
(Linux初始RAM磁盤(initrd)是在系統引導過程中掛載的一個臨時根文件系統,用來支持兩階段的引導過程。initrd文件中包含了各種可執行程序和驅動程序,它們可以用來掛載實際的根文件系統,然后再將這個 initrd RAM磁盤卸載,并釋放內存。在很多嵌入式Linux系統中,initrd 就是最終的根文件系統。)
(可惜關于bootloader這里沒有整個具體例子講講,保持這種感覺欠那么一點的感覺繼續學習。)
前面整了很多虛頭巴老的東西,這個部分來看看實例,一個dts到底有什么東西。
這里先講最近我遇到的三個屬性,內容是來自某位前輩的blog,但是我搞忘鏈接了,抱歉。
設備樹是由節點組成的,節點是由一堆屬性組成的,節點都是具體的設備, 不同的設備需要的屬性不同,用戶可以自定義屬性。
除了用戶自定義屬性,有很多屬性都是標準屬性。
6. compatible屬性
ksms驅動中of_find_compatible_node,就是查找節點的compatible屬性。
compatible屬性也叫做“兼容性”屬性,是一個字符串列表,compatible屬性用于將設備和驅動綁定起來。
字符串列表用于選擇設備所要使用的驅動程序,格式如下所示
“manufacturer,model”
其中manufacturrer表示廠商,model一般是模塊對應的驅動名字。比如像下面這個sound節點
sound {
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";. . . . . .}
屬性分別是“fsl,imx6ul-evk-wm8960”和“fsl,imx-audio-wm8960”,其中“fsl” 表示廠商是飛思卡爾,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驅動模塊名字,這個設備首先使用第一個兼容值在 Linux 內核里面查找,看看能不能找到與之匹配的驅動文件,如果沒有找到的話就使用第二個兼容值查。
設備樹可描述的信息
·CPU的數量和類別。·內存基地址和大小。·總線和橋。·外設連接。·中斷控制器和中斷使用情況。·GPIO控制器和GPIO使用情況。·時鐘控制器和時鐘使用情況。
7、 #address-cells和#size-cells屬性
我們看見dts文件最上面有個 #address-cells和#size-cells。這兩個屬性的值都是無符號32位整型,用于描述子節點的地址信息。
#address-cells屬性值決定了子節點reg屬性中地址信息所占用的字長(32位)
#size-cells屬性值決定了子節點reg屬性中**長度信息所占的字長(**32 位)
整個栗子瞅瞅:
spi4 {
compatible = "spi-gpio";
#address-cells = <1>;
#size-cells = <0>;
gpio_spi: gpio_spi@0 {
compatible = "fairchild,74hc595";
reg = <0>;
};
}
節點 spi4 的#address-cells = <1>, #size-cells = <0>,說明 spi4 的子節點 reg 屬性中起始地址所占用的字長為 1,地址長度所占用的字長為 0。
因為父節點設置了#address cells = <1>, #size-cells = <0>,因此 addres=0,沒有 length 的值,相當于設置了起始地址而沒有設置地址長度。
#address-cells = <2>;
#size-cells = <2>;
相當于reserved-memory設備的屬性的起始地址信息占用的字長為兩位,這個地址長度為兩位。這兩個信息都是針對屬性的。
有點繞,我覺得就是一個是知道了起始地址的位置,然后告訴你從這個起始位置走多遠。
起始地址用多長來描述–》#address-cells = <2>;
走多遠用多長來描述–》#size-cells = <2>;
8、reg屬性
上一部分的#address-cells 和#size-cells 表明了子節點應該如何編寫 reg 屬性值,一般 reg 屬性都是和地址有關的內容,和地址相關的信息有兩種:起始地址和地址長度, reg 屬性的格式一為:
reg = <address1 length1 address2 length2 address3 length3……>
reg 屬性一般用于描述設備地址空間資源信息,一般都是某個外設的寄存器地址范圍信息,比如在 imx6ull.dtsi 中有如下內容:
uart1: serial@02020000 {
compatible = "fsl,imx6ul-uart",
"fsl,imx6q-uart", "fsl,imx21-uart";
reg = <0x02020000 0x4000>;
interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_UART1_IPG>,
<&clks IMX6UL_CLK_UART1_SERIAL>;
clock-names = "ipg", "per";
status = "disabled";
};
(這個肯定對應的 #address-cells = <1>; #size-cells = <1>;)
reg = <0x02020000 0x4000>;
上述代碼是節點 uart1, uart1 節點描述了 I.MX6ULL 的 UART1 相關信息,重點是第 326 行的 reg 屬性。
其中 uart1 的父節點 aips1: aips-bus@02000000 設置了#address-cells = <1>、 #sizecells = <1>,因此 reg 屬性中 address=0x02020000, length=0x4000。
查閱《I.MX6ULL 參考手冊》可知, I.MX6ULL 的 UART1 寄存器首地址為 0x02020000,但是 UART1 的地址長度(范圍)并沒有 0x4000 這么多,這里我們重點是獲取 UART1 寄存器首地址。
上面簡要的了解一丟丟的屬性,其實也最常用的三個屬性。
下面繼續系統學習更多的東西。
9、根節點兼容性
上述.dts文件中,第2行根節點"/"的兼容屬性compatible=“acme,coyotes-revenge”;定義了整個系統(設備級別)的名稱,它的組織形式為:,。
Linux內核通過根節點"/"的兼容屬性即可判斷它啟動的是什么設備。
在真實項目中,這個頂層設備的兼容屬性一般包括兩個或者兩個以上的兼容性字符串,首個兼容性字符串是板子級別的名字,后面一個兼容性是芯片級別(或者芯片系列級別)的名字。
譬如板子arch/arm/boot/dts/vexpress-v2p-ca9.dts兼容于arm,vexpress,v2p-ca9和“arm,vexpress”:
compatible = "arm,vexpress,v2p-ca9", "arm,vexpress";
板子arch/arm/boot/dts/vexpress-v2p-ca5s.dts的兼容性則為:
compatible = "arm,vexpress,v2p-ca5s", "arm,vexpress";
板子arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts的兼容性為:
compatible = "arm,vexpress,v2p-ca15_a7", "arm,vexpress";
可以看出,上述各個電路板的共性是兼容于arm,vexpress,而特性是分別兼容于arm,vexpress,v2p-ca9、arm,vexpress,v2p-ca5s和arm,vexpress,v2p-ca15_a7。
進一步地看,arch/arm/boot/dts/exynos4210-origen.dts的兼容性字段如下:
compatible = "insignal,origen", "samsung,exynos4210", "samsung,exynos4";
第一個字符串是板子名字(很特定),第2個字符
串是芯片名字(比較特定),第3個字段是芯片系列的名字(比較通用)。
作為類比,arch/arm/boot/dts/exynos4210-universal_c210.dts的兼容性字段則如下:
compatible = "samsung,universal_c210", "samsung,exynos4210", "samsung,exynos4";
由此可見,它與exynos4210-origen.dts的區別只在于第1個字符串(特定的板子名字)不一樣,后面芯片名和芯片系列的名字都一樣。
在Linux 2.6內核中,ARM Linux針對不同的電路板會建立由MACHINE_START和MACHINE_END包圍起來的針對這個設備的一系列回調函數,如代碼清單18.3所示。
1 MACHINE_START(VEXPRESS, "ARM-Versatile Express")2 .atag_offset = 0x100,3 .smp = smp_ops(vexpress_smp_ops),4 .map_io = v2m_map_io,5 .init_early = v2m_init_early,6 .init_irq = v2m_init_irq,7 .timer = &v2m_timer,8 .handle_irq = gic_handle_irq,9 .init_machine = v2m_init,
10 .restart = vexpress_restart,
11 MACHINE_END
這些不同的設備會有不同的MACHINE ID,Uboot在啟動Linux內核時會將MACHINE ID存放在r1寄存器,Linux啟動時會匹配Bootloader傳遞的MACHINE ID和MACHINE_START聲明的MACHINE ID,然后執行相應設備的一系列初始化函數。
ARM Linux 3.x在引入設備樹之后,MACHINE_START變更為DT_MACHINE_START,其中含有一個.dt_compat成員,用于表明相關的設備與.dts中根節點的兼容屬性兼容關系。
如果Bootloader傳遞給內核的設備樹中根節點的兼容屬性出現在某設備的.dt_compat表中,相關的設備就與對應的兼容匹配,從而引發這一設備的一系列初始化函數被執行。一個典型的DT_MACHINE如代碼清單18.4所示。
1 static const char * const v2m_dt_match[] __initconst = {2 "arm,vexpress",3 "xen,xenvm",4 NULL,5 };6 DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")7 .dt_compat = v2m_dt_match,8 .smp = smp_ops(vexpress_smp_ops),9 .map_io = v2m_dt_map_io,
10 .init_early = v2m_dt_init_early,
11 .init_irq = v2m_dt_init_irq,
12 .timer = &v2m_dt_timer,
13 .init_machine = v2m_dt_init,
14 .handle_irq = gic_handle_irq,
15 .restart = vexpress_restart,
16 MACHINE_END
Linux倡導針對多個SoC、多個電路板的通用DT設備,即一個DT設備的.dt_compat包含多個電路板.dts文件的根節點兼容屬性字符串。 (舉個栗子就是這個流程 這個那個板子都可以用)
之后,如果這多個電路板的初始化序列不一樣,可以通過int of_machine_is_compatible(const char*compat)API判斷具體的電路板是什么。在Linux內核中,常常使用如下API來判斷根節點的兼容性:
int of_machine_is_compatible(const char *compat);
此API判斷目前運行的板子或者SoC的兼容性,它匹配的是設備樹根節點下的兼容屬性。
例如drivers/cpufreq/exynos-cpufreq.c中就有判斷運行的CPU類型是exynos4210、exynos4212、exynos4412還是exynos5250的代碼,進而分別處理,如代碼清單18.5所示。
1 static int exynos_cpufreq_probe(struct platform_device *pdev)2 {3 int ret = -EINVAL;45 exynos_info = kzalloc(sizeof(*exynos_info), GFP_KERNEL);6 if (!exynos_info)7 return -ENOMEM;89 exynos_info->dev = &pdev->dev;
10
11 if (of_machine_is_compatible("samsung,exynos4210")) {
12 exynos_info->type = EXYNOS_SOC_4210;
13 ret = exynos4210_cpufreq_init(exynos_info);
14 } else if (of_machine_is_compatible("samsung,exynos4212")) {
15 exynos_info->type = EXYNOS_SOC_4212;
16 ret = exynos4x12_cpufreq_init(exynos_info);
17 } else if (of_machine_is_compatible("samsung,exynos4412")) {
18 exynos_info->type = EXYNOS_SOC_4412;
19 ret = exynos4x12_cpufreq_init(exynos_info);
20 } else if (of_machine_is_compatible("samsung,exynos5250")) {
21 exynos_info->type = EXYNOS_SOC_5250;
22 ret = exynos5250_cpufreq_init(exynos_info);
23 } else {
24 pr_err("%s: Unknown SoC type\n", __func__);
25 return -ENODEV;
26 }
27 ...
28 }
如果一個兼容包含多個字符串,譬如對于前面介紹的根節點兼容compatible=“samsung,universal_c210”,“samsung,exynos4210”,"samsung,exynos4"的情況,如下3個表達式都是成立的。
of_machine_is_compatible("samsung,universal_c210")
of_machine_is_compatible("samsung,exynos4210")
of_machine_is_compatible("samsung,exynos4")
這個的復用吧,我覺得是利用一套去適應多條的必然,但是如果相對于本身,順序或者自定義過多,那么其實也沒有必要復用這個DT吧。具體的當然我現在的理解都是純靠猜想,具體等到設計到的時候,再做詳細展開,正如我所說,學習讀書,聯接很重要。
上面學了根節點,這里來學習設備節點:
內容來自:《Linux設備驅動》
在.dts文件的每個設備節點中,都有一個兼容屬性,兼容屬性用于驅動和設備的綁定。兼**容屬性是一個字符串的列表,列表中的第一個字符串表征了節點代表的確切設備,**形式為",",其后的字符串表征可兼容的其他設備。可以說前面的是特指,后面的則涵蓋更廣的范圍。如在vexpress-v2m.dtsi中的Flash節點如下:
(根節點是綁定更高層級的,比如芯片板子,設備節點就是具體的設備)
flash@0,00000000 {
compatible = "arm,vexpress-flash", "cfi-flash";
reg = <0 0x00000000 0x04000000>,
<1 0x00000000 0x04000000>;
bank-width = <4>;};
兼容屬性的第2個字符串"cfi-flash"明顯比第1個字符串"arm,vexpress-flash"涵蓋的范圍更廣。
再如,Freescale MPC8349SoC含一個串口設備,它實現了國家半導體(National Sem-iconductor)的NS16550寄存器接口。則MPC8349串口設備的兼容屬性為compatible=“fsl,mpc8349-uart”,“ns16550”。其中,fsl,mpc8349-uart指代了確切的設備,ns16550代表該設備與NS16550UART保持了寄存器兼容。因此,設備節點的兼容性和根節點的兼容性是類似的,都是“從具體到抽象”。
使用設備樹后,**驅動需要與.dts中描述的設備節點進行匹配,從而使驅動的probe()函數執行。**對于platform_driver而言,需要添加一個OF匹配表,如前文的.dts文件的"acme,a1234-i2c-bus"兼容I2C控制器節點的OF匹配表,具體代碼清單18.6所示。
1 static const struct of_device_id a1234_i2c_of_match[] = {2 { .compatible = "acme,a1234-i2c-bus", },3 {},4 };5 MODULE_DEVICE_TABLE(of, a1234_i2c_of_match);67 static struct platform_driver i2c_a1234_driver = {8 .driver = {9 .name = "a1234-i2c-bus",
10 .owner = THIS_MODULE,
11 .of_match_table = a1234_i2c_of_match,
12 },
13 .probe = i2c_a1234_probe,
14 .remove = i2c_a1234_remove,
15 };
16 module_platform_driver(i2c_a1234_driver);
對于I2C和SPI從設備而言,同樣也可以通過of_match_table添加匹配的.dts中的相關節點的兼容屬性,如sound/soc/codecs/wm8753.c中的針對WolfsonWM8753的of_match_table,具體如代碼清單18.7所示。
1 static const struct of_device_id wm8753_of_match[] = {2 { .compatible = "wlf,wm8753", },3 { }4 };5 MODULE_DEVICE_TABLE(of, wm8753_of_match);6 static struct spi_driver wm8753_spi_driver = {7 .driver = {8 .name = "wm8753",9 .owner = THIS_MODULE,
10 .of_match_table = wm8753_of_match,
11 },
12 .probe = wm8753_spi_probe,
13 .remove = wm8753_spi_remove,
14 };
15 static struct i2c_driver wm8753_i2c_driver = {
16 .driver = {
17 .name = "wm8753",
18 .owner = THIS_MODULE,
19 .of_match_table = wm8753_of_match,
20 },
21 .probe = wm8753_i2c_probe,
22 .remove = wm8753_i2c_remove,
23 .id_table = wm8753_i2c_id,
24 };
(其實看了這個部分,對于前面根節點應該也有所感悟,應該到這里既知道了怎么去看dts,應該知道了怎么將屬性和driver的probe綁定起來。)
上述代碼中的第2行顯示WM8753的供應商是“wlf”,它其實是對應于Wolfson Microe-lectronics的前綴。詳細的前綴可見于內核文檔:Documentation/devicetree/bindings/vendor-prefixes.txt
1 static int spi_match_device(struct device *dev, struct device_driver *drv)2 {3 const struct spi_device *spi = to_spi_device(dev);4 const struct spi_driver *sdrv = to_spi_driver(drv);56 /* Attempt an OF style match */7 if (of_driver_match_device(dev, drv))8 return 1;9
10 /* Then try ACPI */
11 if (acpi_driver_match_device(dev, drv))
12 return 1;
13
14 if (sdrv->id_table)
15 return !!spi_match_id(sdrv->id_table, spi);
16
17 return strcmp(spi->modalias, drv->name) == 0;
18 }
19 static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
20 const struct spi_device *sdev)
21 {
22 while (id->name[0]) {
23 if (!strcmp(sdev->modalias, id->name))
24 return id;
25 id++;
26 }
27 return NULL;
28 }
通過這個別名匹配,實際上,SPI和I2C的外設驅動即使沒有of_match_table,還是可以和設備樹中的節點匹配上的。
一個驅動可以在of_match_table中兼容多個設備,在Linux內核中常常使用如下API來判斷具體的設備是什么:
int of_device_is_compatible(const struct device_node *device,const char *compat);
此函數用于判斷設備節點的兼容屬性是否包含compat指定的字符串。這個API多用于一個驅動支持兩個以上設備的時候。
當一個驅動支持兩個或多個設備的時候,這些不同.dts文件中設備的兼容屬性都會寫入驅動OF匹配表。
因此驅動可以通過Bootloader **傳遞給內核設備樹中的真正節點的兼容屬性以確定究竟是哪一種設備,**從而根據不同的設備類型進行不同的處理。
如arch/powerpc/platforms/83xx/usb.c中的mpc831x_usb_cfg()就進行了類似處理:
if (immr_node && (of_device_is_compatible(immr_node, "fsl,mpc8315-immr") ||of_device_is_compatible(immr_node, "fsl,mpc8308-immr")))clrsetbits_be32(immap + MPC83XX_SCCR_OFFS,MPC8315_SCCR_USB_MASK,MPC8315_SCCR_USB_DRCM_01);
elseclrsetbits_be32(immap + MPC83XX_SCCR_OFFS,MPC83XX_SCCR_USB_MASK,MPC83XX_SCCR_USB_DRCM_11);
/* Configure pin mux for ULPI. There is no pin mux for UTMI */
if (prop && !strcmp(prop, "ulpi")) {
if (of_device_is_compatible(immr_node, "fsl,mpc8308-immr")) {clrsetbits_be32(immap + MPC83XX_SICRH_OFFS,MPC8308_SICRH_USB_MASK,MPC8308_SICRH_USB_ULPI);} else if (of_device_is_compatible(immr_node, "fsl,mpc8315-immr")) {clrsetbits_be32(immap + MPC83XX_SICRL_OFFS,MPC8315_SICRL_USB_MASK,MPC8315_SICRL_USB_ULPI);clrsetbits_be32(immap + MPC83XX_SICRH_OFFS,MPC8315_SICRH_USB_MASK,MPC8315_SICRH_USB_ULPI);} else {clrsetbits_be32(immap + MPC83XX_SICRL_OFFS,MPC831X_SICRL_USB_MASK,MPC831X_SICRL_USB_ULPI);clrsetbits_be32(immap + MPC83XX_SICRH_OFFS,MPC831X_SICRH_USB_MASK,MPC831X_SICRH_USB_ULPI);}
}
它根據具體的設備是fsl,mpc8315-immr和fsl,mpc8308-immr、中的哪一種來進行不同的處理。
當一個驅動可以兼容多種設備的時候,除了of_device_is_compatible()這種判斷方法以外,還可以**采用在驅動的of_device_id表中填充.data成員的形式。**譬如,arch/arm/mm/cache-l2x0.c支持“arm,l210-cache”“arm,pl310-cache”“arm,l220-cache”等多種設備,其of_device_id表如代碼清單18.9所示。
1 #define L2C_ID(name, fns) { .compatible = name, .data = (void *)&fns }2 static const struct of_device_id l2x0_ids[] __initconst = {3 L2C_ID("arm,l210-cache", of_l2c210_data),4 L2C_ID("arm,l220-cache", of_l2c220_data),5 L2C_ID("arm,pl310-cache", of_l2c310_data),6 L2C_ID("brcm,bcm11351-a2-pl310-cache", of_bcm_l2x0_data),7 L2C_ID("marvell,aurora-outer-cache", of_aurora_with_outer_data),8 L2C_ID("marvell,aurora-system-cache", of_aurora_no_outer_data),9 L2C_ID("marvell,tauros3-cache", of_tauros3_data),
10 /* Deprecated IDs */
11 L2C_ID("bcm,bcm11351-a2-pl310-cache", of_bcm_l2x0_data),
12 {}
13 };
在驅動中,通過如代碼清單18.10的方法拿到了對應于L2緩存類型的.data成員,其中主要用到了of_match_node()這個API。
1 int __init l2x0_of_init(u32 aux_val, u32 aux_mask)2 {3 const struct l2c_init_data *data;4 struct device_node *np;56 np = of_find_matching_node(NULL, l2x0_ids);7 if (!np)8 return -ENODEV;9 …
10 data = of_match_node(l2x0_ids, np)->data;
11 }
如果電路板的.dts文件中L2緩存是arm,pl310-cache,那么上述代碼第10行找到的data就是of_l2c310_data,它是l2c_init_data結構體的一個實例。
l2c_init_data是一個由L2緩存驅動自定義的數據結構,在其定義中既可以保護數據成員,又可以包含函數指針,如代碼清單18.11所示。
1 struct l2c_init_data {2 const char *type;3 unsigned way_size_0;4 unsigned num_lock;5 void (*of_parse)(const struct device_node *, u32 *, u32 *);6 void (*enable)(void __iomem *, u32, unsigned);7 void (*fixup)(void __iomem *, u32, struct outer_cache_fns *);8 void (*save)(void __iomem *);9 struct outer_cache_fns outer_cache;
10 };
通過這種方法,驅動可以把與某個設備兼容的私有數據尋找出來,如此體現了一種面向對象的設計思想,避免了大量的if,else或者switch,case語句。
(如果現在不能理解,那么我要和你握個手,我也是。但是當有天真的涉及到這個場景的時候,知道回來看看,那么就不錯。現在你的知識還沒足夠和它產生聯接,所以看過沒啥感覺也是一樣的。)
10、地址編碼
可尋址的設備使用如下信息在設備樹中編碼地址信息:
reg#address-cells#size-cells
其中,reg的組織形式為
reg=<address1length1[address2length2][address3length3]...>
其中的每一組address length表明了設備使用的一個地址范圍。
address為1個或多個32位的整型(即cell),而length的意義則意味著從address到address+length–1的地址范圍都屬于該節點。若#size-cells=0,則length字段為空。
address和length字段是可變長的,父節點的#address-cells和#size-cells分別決定了子節點reg屬性的address和length字段的長度。
在代碼清單18.2中,根節點的#address-cells=<1>;和#size-cells=<1>;決定了serial、gpio、spi等節點的address和length字段的長度分別為1。
cpus節點的#address-cells=<1>;和#size-cells=<0>;決定了兩個cpu子節點的address為1,而length為空,于是形成了兩個cpu的reg=<0>;和reg=<1>;。
external-bus節點的#address-cells=<2>和#size-cells=<1>;
決定了其下的ethernet、i2c、flash的reg字段形如reg=<0 00 x1000>;、reg=<1 00 x1000>;和reg=<2 00 x4000000>;。
其中,address字段長度為2,開始的第一個cell(即“<”后的0、1、2)是對應的片選,第2個cell(即<000x1000>、<100x1000>和<200x1000000>中間的0,0,0)是相對該片選的基地址,第3個cell(即“>”前的0x1000、0x1000、0x1000000)為length。
特別要留意的是i2c節點中定義的#address-cells=<1>;和#size-cells=<0>;,其作用到了I2C總線上連接的RTC,它的address字段為0x58,是RTC設備的I2C地址。(這個意思就是你就在這里,哪里都不需要去)
根節點的直接子書點描述的是CPU的視圖,因此根子節點的address區域就直接位于CPU的內存區域。
但是,經過總線橋后的address往往需要經過轉換才能對應CPU的內存映射。
11、ranges
external-bus的ranges屬性定義了經過external-bus橋后的地址范圍如何映射到CPU的內存區域**。**
(設備的內存管理)
ranges是地址轉換表,其中的每個項目是一個子地址、父地址以及在子地址空間的大小的映射。
映射表中的子地址、父地址分別采用子地址空間的#address-cells和父地址空間的#address-cells大小。對于本例而言,子地址空間的#address-cells為2,父地址空間的#address-cells值為1,因此0 0 0x10100000 0x10000的
前2個cell為external-bus橋后external-bus上片選0偏移0,(子地址)
第3個cell表示external-bus上片選0偏移0的地址空間被映射到CPU的本地總線的0x10100000位置, (父地址)
第4個cell表示映射的大小為0x10000。 ranges后面兩個項目的含義可以類推。(長度length)
ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet1 0 0x10160000 0x10000 // Chipselect 2, i2c controller2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
12、中斷連接
設備樹竟然還可以保存中斷信息。對于中斷與硬件的關系,硬件怎么去觸發中斷,實現中斷。有機會看看研究一下,中斷。回到正題。
對于中斷控制器而言,它提供如下屬性:**interrupt-controller–這個屬性為空,**中斷控制器應該加上此屬性表明自己的身份;
#interrupt-cells–與#address-cells和#size-cells相似,它表明連接此中斷控制器的設備的中斷屬性的cell大小。
在整個設備樹中,與中斷相關的屬性還包括:
interrupt-parent–設備節點通過它來指定它所依附的中斷控制器的phandle,當節點沒有指定interrupt-parent時,則從父級節點繼承。對于本例(代碼清單18.2)而言,根節點指定了interrupt-parent=<&intc>;,其對應于intc:interrupt-controller@10140000,而根節點的子節點并未指定interrupt-parent,因此它們都繼承了intc,即位于0x10140000的中斷控制器中。
interrupts–用到了中斷的設備節點,通過它指定中斷號、觸發方法等,這個屬性具體含有多少個cell,由它依附的中斷控制器節點的#interrupt-cells屬性決定。
而每個cell具體又是什么含義,一般由驅動的實現決定,而且也會在設備樹的綁定文檔中說明。譬如,對于ARM GIC中斷控制器而言,#interrupt-cells為3,3個cell的具體含義在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 triggered2 = high-to-low edge triggered4 = active high level-sensitive8 = 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.
另外,值得注意的是,一個設備還可能用到多個中斷號。對于ARM GIC而言,若某設備使用了SPI的168號、169號兩個中斷,而且都是高電平觸發,則該設備節點的中斷屬性可定義為interrupts=<01684>,<01694>;。
對于平臺設備而言,簡單的通過如下API就可以指定想取哪一個中斷,其中的參數num就是中斷的index。
int platform_get_irq(struct platform_device *dev, unsigned int num);
當然在.dts文件中可以對中斷進行命名,而后在驅動中通過platform_get_irq_byname()來獲取對應的中斷號。
譬如代碼清單18.14演示了在drivers/dma/fsl-edma.c中通過platform_get_irq_byname()獲取IRQ,以及arch/arm/boot/dts/vf610.dtsi與fsl-edma驅動對應節點的中斷描述。
1 static int2 fsl_edma_irq_init(struct platform_device *pdev,struct fsl_edma_engine *fsl_edma)3 {4 fsl_edma->txirq = platform_get_irq_byname(pdev, "edma-tx");5 fsl_edma->errirq = platform_get_irq_byname(pdev, "edma-err");6 }78 edma0: dma-controller@40018000 {9 #dma-cells = <2>;
10 compatible = "fsl,vf610-edma";
11 reg = <0x40018000 0x2000>,
12 <0x40024000 0x1000>,
13 <0x40025000 0x1000>;
14 interrupts = <0 8 IRQ_TYPE_LEVEL_HIGH>,
15 <0 9 IRQ_TYPE_LEVEL_HIGH>;
16 interrupt-names = "edma-tx", "edma-err";
17 dma-channels = <32>;
18 clock-names = "dmamux0", "dmamux1";
19 clocks = <&clks VF610_CLK_DMAMUX0>,
20 <&clks VF610_CLK_DMAMUX1>;
21 };
第4行、第5行的platform_get_irq_byname()的第2個參數與.dts中的interrupt-names是一致的。
除了中斷以外,在ARM Linux中時鐘、GPIO、pinmux都可以通過.dts中的節點和屬性進行描述。
(pinmux—Pin Multiplexing縮寫,即引腳復用,通常CPU的管腳數量有限,通過復用來實現不同功能)
這設備樹真的是很全面?還記得第一篇說dts可以描述的硬件信息有哪些嗎?
·CPU的數量和類別。·內存基地址和大小。·總線和橋。·外設連接。·中斷控制器和中斷使用情況。·GPIO控制器和GPIO使用情況。·時鐘控制器和時鐘使用情況。
13、GPIO
譬如,對于GPIO控制器而言,其對應的設備節點需聲明gpio-controller屬性,并設置#gpio-cells的大小。
譬如,對于兼容性為fsl,imx28-pinctrl的pinctrl驅動而言,其GPIO控制器的設備節點類似于:
pinctrl@80018000 {
compatible = "fsl,imx28-pinctrl", "simple-bus";
reg = <0x80018000 2000>;gpio0: gpio@0 {
compatible = "fsl,imx28-gpio";
interrupts = <127>;
gpio-controller;#gpio-cells = <2>;
interrupt-controller;#interrupt-cells = <2>;};gpio1: gpio@1 {
compatible = "fsl,imx28-gpio";
interrupts = <126>;
gpio-controller;#gpio-cells = <2>;
interrupt-controller;#interrupt-cells = <2>;};...
};
其中,#gpio-cells為2,第1 個cell為GPIO號,第2個為GPIO的極性。為0的時候是高電平有效,為1的時候則是低電平有效。
使用GPIO的設備則通過定義命名xxx-gpios屬性來引用GPIO控制器的設備節點,如:
sdhci@c8000400 {
status = "okay";
cd-gpios = <&gpio01 0>;
wp-gpios = <&gpio02 0>;
power-gpios = <&gpio03 0>;
bus-width = <4>;};
而具體的設備驅動則通過類似如下的方法來獲取GPIO:
cd_gpio = of_get_named_gpio(np, "cd-gpios", 0);
wp_gpio = of_get_named_gpio(np, "wp-gpios", 0);
power_gpio = of_get_named_gpio(np, "power-gpios", 0);
of_get_named_gpio()這個API的原型如下:
static inline int of_get_named_gpio(struct device_node *np,
const char *propname, int index);
在.dts和設備驅動不關心GPIO名字的情況下,也可以直接通過of_get_gpio()獲取GPIO,此函數原型為:
static inline int of_get_gpio(struct device_node *np, int index);
如對于compatible="gpio-control-nand"的基于GPIO的NAND控制器而言,在.dts中會定義多個gpio屬性:
gpio-nand@1,0 {
compatible = "gpio-control-nand";
reg = <1 0x0000 0x2>;#address-cells = <1>;#size-cells = <1>;
gpios = <&banka 1 0 /* rdy */
&banka 2 0 /* nce */
&banka 3 0 /* ale */
&banka 4 0 /* cle */0 /* nwp */>;partition@0 {...};
};
在相應的驅動代碼drivers/mtd/nand/gpio.c中是這樣獲取這些GPIO的:
plat->gpio_rdy = of_get_gpio(dev->of_node, 0);
plat->gpio_nce = of_get_gpio(dev->of_node, 1);
plat->gpio_ale = of_get_gpio(dev->of_node, 2);
plat->gpio_cle = of_get_gpio(dev->of_node, 3);
plat->gpio_nwp = of_get_gpio(dev->of_node, 4);
14、時鐘
時鐘和GPIO也是類似的,時鐘控制器的節點被使用時鐘的模塊引用:
clocks = <&clks 138>, <&clks 140>, <&clks 141>;
clock-names = "uart", "general", "noc";
而驅動中則使用上述的clock-names屬性作為clk_get()或devm_clk_get()的第二個參數來申請時鐘,譬如獲取第2個時鐘:
devm_clk_get(&pdev->dev, "general");
<&clks 138>里的138這個index是與相應時鐘驅動中clk的表的順序對應的,很多開發者也認為這種數字出現在設備樹中不太好,因此他們把clk的index作為宏定義到了arch/arm/boot/dts/include/dt-bindings/clock中。譬如include/dt-bindings/clock/imx6qdl-clock.h中存在這樣的宏:
#define IMX6QDL_CLK_STEP 16
#define IMX6QDL_CLK_PLL1_SW 17…
#define IMX6QDL_CLK_ARM 104…
而arch/arm/boot/dts/imx6q.dtsi則是這樣引用它們的:
clocks = <&clks IMX6QDL_CLK_ARM>,
<&clks IMX6QDL_CLK_PLL2_PFD2_396M>,
<&clks IMX6QDL_CLK_STEP>,
<&clks IMX6QDL_CLK_PLL1_SW>,
<&clks IMX6QDL_CLK_PLL1_SYS>;
15、pinmux
在設備樹中,**某個設備節點使用的pinmux的引腳群是通過phandle來指定的。**譬如在arch/arm/boot/dts/atlas6.dtsi的pinctrl節點中包含所有引腳群的描述,如代碼清單18.15所示。
1 gpio: pinctrl@b0120000 {2 #gpio-cells = <2>;3 #interrupt-cells = <2>;4 compatible = "sirf,atlas6-pinctrl";5 …67 lcd_16pins_a: lcd0@0 {8 lcd {9 sirf,pins = "lcd_16bitsgrp";
10 sirf,function = "lcd_16bits";
11 };
12 };
13 …
14 spi0_pins_a: spi0@0 {
15 spi {
16 sirf,pins = "spi0grp";
17 sirf,function = "spi0";
18 };
19 };
20 spi1_pins_a: spi1@0 {
21 spi {
22 sirf,pins = "spi1grp";
23 sirf,function = "spi1";
24 };
25 };
26 …
27 };
而SPI0這個硬件實際上需要用到spi0_pins_a對應的spi0grp這一組引腳,因此在atlas6-evb.dts中通過pinctrl-0引用了它,如代碼清單18.16所示。
1 spi@b00d0000 {2 status = "okay";3 pinctrl-names = "default";4 pinctrl-0 = <&spi0_pins_a>;5 …6 };
到目前為止,我們可以勾勒出一個設備樹的全局視圖,圖18.2顯示了設備樹中的節點、屬性、label以及phandle等信息。
下面看看當有了設備樹對BSP產生了什么影響?
16、設備點屬性回顧
前面根節點“/”的cpus子節點下面又包含兩個cpu子節點,描述了此設備上的兩個CPU,并且兩者的兼容屬性為:“arm,cortex-a9”。
注意cpus和cpus的兩個cpu子節點的命名,它們遵循的組織形式為[@],<>中的內容是必選項,[]中的則為可選項。
name是一個ASCII字符串,用于描述節點對應的設備類型,如3com Ethernet適配器對應的節點name宜為ethernet,而不是3com509。
如果一個節點描述的設備有地址,則應該給出@unit-address。多個相同類型設備節點的name可以一樣,只要unit-address不同即可,如本例中含有cpu@0、cpu@1以及serial@101f0000與serial@101f2000這樣的同名節點。設備的unit-address地址也經常在其對應節點的reg屬性中給出。
**對于掛在內存空間的設備而言,@字符后跟的一般就是該設備在內存空間的基地址,**譬如arch/arm/boot/dts/exynos4210.dtsi中存在的:
sysram@02020000 {compatible = "mmio-sram";reg = <0x02020000 0x20000>;…
}
上述節點的reg屬性的開始位置與@后面的地址一樣。
對于掛在I2C總線上的外設而言,@后面一般跟的是從設備的I2C地址,譬如arch/arm/boot/dts/exynos4210-trats.dts中的mms114-touchscreen:
i2c@13890000 {…mms114-touchscreen@48 {compatible = "melfas,mms114";reg = <0x48>;…};
};
具體的節點命名規范可見ePAPR(embedded Power Architecture Platform Reference)標準,在https://www.power.org中可下載該標準。
17、設備添加label
我們還可以給一個設備節點添加label,之后可以通過&label的形式訪問這個label,這種引用是通過phandle(pointer handle)進行的。
例如,在arch/arm/boot/dts/omap5.dtsi中,第3組GPIO有gpio3這個label,如代碼清單18.12所示。
1 gpio3: gpio@48057000 {2 compatible = "ti,omap4-gpio";3 reg = <0x48057000 0x200>;4 interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;5 ti,hwmods = "gpio3";6 gpio-controller;7 #gpio-cells = <2>;8 interrupt-controller;9 #interrupt-cells = <2>;
10 };
而hsusb2_phy這個USB的PHY復位GPIO用的是這組GPIO中的一個,所以它通過phandle引用了“gpio3”,如代碼清單18.13所示。
1 /* HS USB Host PHY on PORT 2 */2 hsusb2_phy: hsusb2_phy {3 compatible = "usb-nop-xceiv";4 reset-gpios = <&gpio3 12 GPIO_ACTIVE_LOW>; /* gpio3_76 HUB_RESET */5 };
代碼的第1行的gpio3是gpio@48057000節點的label,而代碼清單18.13的hsusb2_phy則通過&gpio3引用了這個節點,表明自己要使用這一組GPIO中的第12個GPIO。很顯然,這種phandle引用其實表明硬件之間的一種關聯性(這不就是匯編的函數,然后調函數。)
再舉一例,在arch/arm/boot/dts/omap5.dtsi中,我們可以看到類似如下的label,從這些實例可以看出,label習慣以<設備類型>進行命名:
i2c1: i2c@48070000 {
}
i2c2: i2c@48072000 {
}
i2c3: i2c@48060000 {
}…
讀者也許發現了一個奇怪的現象,就是代碼清單18.13中居然引用了GPIO_ACTIVE_LOW這個類似C語言的宏。文件.dts的編譯過程確實支持C的預處理,相應的.dts文件也包括了包含GPIO_ACTIVE_LOW這個宏定義的頭文件:
#include <dt-bindings/gpio/gpio.h>
對于ARM而言,dt-bindings頭文件位于內核的arch/arm/boot/dts/include/dt-bindings目錄中。觀察該目錄的屬性,它實際上是一個符號鏈接:
baohua@baohua-VirtualBox:~/develop/linux/arch/arm/boot/dts/include$ ls -l dt-bindings
lrwxrwxrwx 1 baohua baohua 34 11月 28 00:16 dt-bindings -> ../../../../../include/dt-bindings
從內核的scripts/Makefile.lib這個文件可以看出,文件.dts的編譯過程確實是支持C預處理的。
cmd_dtc = $(CPP) $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \$(objtree)/scripts/dtc/dtc -O dtb -o $@ -b 0 \-i $(dir $<) $(DTC_FLAGS) \-d $(depfile).dtc.tmp $(dtc-tmp) ; \
cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile)
怎么看出來的?
是因為它是先做了
$(CPP)$(dtc_cpp_flags)-x assembler-with-cpp-o$(dtc-tmp)$<
再做的.dtc編譯。
先鏈接好了,才編譯.dtc的。
除了前文介紹的of_machine_is_compatible()、of_device_is_compatible()等常用函數以外,在Linux的BSP和驅動代碼中,經常會使用到一些Linux中其他設備樹的API,這些API通常被冠以of_前綴,它們的實現代碼位于內核的drivers/of目錄下。這些常用的API包括下面內容。
1.尋找節點
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compatible);
根據兼容屬性,獲得設備節點。
遍歷設備樹中的設備節點,看看哪個節點的類型、兼容屬性與本函數的輸入參數匹配,在大多數情況下,from、type為NULL,則表示遍歷了所有節點。
2.讀取屬性
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中,通過如下語句可讀取L2cache的"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"屬性的L2cache節點如下:
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中,如代碼清單18.19所示。
1 static inline int of_property_read_u8(const struct device_node *np,2 const char *propname,3 u8 *out_value)4 {5 return of_property_read_u8_array(np, propname, out_value, 1);6 }78 static inline int of_property_read_u16(const struct device_node *np,9 const char *propname,
10 u16 *out_value)
11{
12 return of_property_read_u16_array(np, propname, out_value, 1);
13}
14
15 static inline int of_property_read_u32(const struct device_node *np,
16 const char *propname,
17 u32 *out_value)
18 {
19 return of_property_read_u32_array(np, propname, out_value, 1);
20 }
除了整型屬性外,字符串屬性也比較常用,其對應的API包括:
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 const char *of_clk_get_parent_name(struct device_node *np, int index)2 {3 struct of_phandle_args clkspec;4 const char *clk_name;5 int rc;67 if (index < 0)8 return NULL;9
10 rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
11 &clkspec);
12 if (rc)
13 return NULL;
14
15 if (of_property_read_string_index(clkspec.np, "clock-output-names",
16 clkspec.args_count clkspec.args[0] : 0,
17 &clk_name) < 0)
18 clk_name = clkspec.np->name;
19
20 of_node_put(clkspec.np);
21 return clk_name;
22 }
23 EXPORT_SYMBOL_GPL(of_clk_get_parent_name);
除整型、字符串以外的最常用屬性類型就是布爾型,其對應的API很簡單,具體如下
static inline bool of_property_read_bool(const struct device_node *np,
const char *propname);
如果設備節點np含有propname屬性,則返回true,否則返回false。一般用于檢查空屬性是否存在。
3.內存映射
void __iomem *of_iomap(struct device_node *node, int index);
上述API可以直接通過設備節點進行設備內存區間的ioremap(),index是內存段的索引。
若設備節點的reg屬性有多段,可通過index標示要ioremap()的是哪一段,在只有1段的情況,index為0。
采用設備樹后,一些設備驅動通過of_iomap()而不再通過傳統的ioremap()進行映射,當然,傳統的ioremap()的用戶也不少。
int of_address_to_resource(struct device_node *dev, int index, struct resource *r);
上述API通過設備節點獲取與它對應的內存資源的resource結構體。
其本質是分析reg屬性以獲取內存基地址、大小等信息并填充到struct resource*r參數指向的結構體中。
4.解析中斷
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
通過設備樹獲得設備的中斷號,實際上是從.dts中的interrupts屬性里解析出中斷號。若設備使用了多個中斷,index指定中斷的索引號。
5.獲取與節點對應的platform_device
struct platform_device *of_find_device_by_node(struct device_node *np);
在可以拿到device_node的情況下,如果想反向獲取對應的platform_device,可使用上述API。
當然,在已知platform_device的情況下,想獲取device_node則易如反掌,例如:
static int sirfsoc_dma_probe(struct platform_device *op)
{struct device_node *dn = op->dev.of_node;…
}
[一個現實的Linux設備和驅動通常都需要掛接在一種總線上,對于本身依附于PCI、USB、I2C、SPI等的設備而言,這自然不是問題,但是在嵌入式系統里面,**在SoC系統中集成的獨立外設控制器、掛接在SoC內存空間的外設等卻不依附于此類總線。**基于這一背景,Linux發明了一種虛擬的總線,稱為platform總線,相應的設備稱為platform_device,而驅動成為platform_driver。]
6. 打個總結
充斥著ARM社區的大量垃圾代碼導致Linus盛怒,因此該社區在2011~2012年進行了大量的修整工作。
ARM Linux開始圍繞設備樹展開,設備樹有自己的獨立語法,它的源文件為.dts,編譯后得到.dtb,Bootloader在引導Linux內核的時候會將.dtb地址告知內核。(通過dtc)
(這也是有的會有個dtb鏡像)
之后內核會展開設備樹并創建和注冊相關的設備,因此arch/arm/mach-xxx和arch/arm/plat-xxx中的大量用于注冊platform、I2C、SPI等板級信息的代碼被刪除,而驅動也以新的方式與在.dts中定義的設備節點進行匹配。
設備樹就到這里,其實很難都一次性掌握好,需要不斷地實踐應用才會變成長時記憶,學習的路還很長。
有了設備樹后,不再需要大量的板級信息,譬如過去經常在arch/arm/plat-xxx和arch/arm/mach-xxx中實施如下事情。
下面展示的都是改變了的,有個對比可以讓你對設備樹的影響更加的深刻。
1、注冊platform_device,綁定resource,即內存、IRQ等板級信息
通過設備樹后,形如:
static struct resource xxx_resources[] = {[0] = {.start = …,.end = …,.flags = IORESOURCE_MEM,},[1] = {.start = …,.end = …,.flags = IORESOURCE_IRQ,},
};
static struct platform_device xxx_device = {.name = "xxx",.id = -1,.dev = {.platform_data = &xxx_data,},.resource = xxx_resources,.num_resources = ARRAY_SIZE(xxx_resources),
};
之類的platform_device代碼都不再需要,其中**platform_device會由內核自動展開。**而這些resource實際來源于.dts 中設備節點的reg、interrupts屬性。
典型的,大多數總線都與“simple_bus”兼容,而在與SoC對應的設備的.init_machine成員函數中,調用of_platform_bus_probe(NULL,xxx_of_bus_ids,NULL);即可自動展開所有的platform_device。
2、注冊i2c_board_info,指定IRQ等板級信息
形如:
static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {{I2C_BOARD_INFO("tlv320aic23", 0x1a),}, {I2C_BOARD_INFO("fm3130", 0x68),}, {I2C_BOARD_INFO("24c64", 0x50),},
};
之類的i2c_board_info代碼目前不再需要出現,現在只需要把tlv320aic23、fm3130、24c64這些設備節點填充作為相應的I2C控制器節點的子節點即可,類似于前面的代碼:
i2c@1,0 {compatible = "acme,a1234-i2c-bus";…rtc@58 {compatible = "maxim,ds1338";reg = <58>;interrupts = < 7 3 >;};};
設備樹中的I2C客戶端會通過在I2C host驅動的probe()函數中調用的of_i2c_register_devices(&i2c_dev->adapter);被自動展開。
3、注冊spi_board_info,指定IRQ等板級信息
static struct spi_board_info afeb9260_spi_devices[] = {{ /* DataFlash chip */.modalias = "mtd_dataflash",.chip_select = 1,.max_speed_hz = 15 * 1000 * 1000,.bus_num = 0,},
};
之類的spi_board_info代碼目前不再需要出現,與I2C類似,現在只需要把mtd_dataflash之類的節點作為SPI控制器的子節點即可,SPI host驅動的probe()函數通過spi_register_master()注冊主機的時候,會自動展開依附于它的從機,spear1310-evb.dts中的st,m25p80SPI接口的NOR Flash節點如下:
之類的spi_board_info代碼目前不再需要出現,與I2C類似,現在只需要把mtd_dataflash之類的節點作為SPI控制器的子節點即可,SPI host驅動的probe()函數通過spi_register_master()注冊主機的時候,會自動展開依附于它的從機,spear1310-evb.dts中的st,m25p80SPI接口的NOR Flash節點如下:
4、多個針對不同電路板的設備,以及相關的回調函數
在過去,ARM Linux針對不同的電路板會建立由MACHINE_START和MACHINE_END包圍的設備,引入設備樹之后,MACHINE_START變更為DT_MACHINE_START,其中含有一個.dt_compat成員,用于表明相關的設備與.dts中根節點的兼容屬性的兼容關系。
這樣可以顯著改善代碼的結構并減少冗余的代碼,在不支持設備樹的情況下,光是一個S3C24xx就存在多個板文件,譬如mach-amlm5900.c、mach-gta02.c、mach-smdk2410.c、mach-qt2410.c、mach-rx3715.c等,其累計的代碼量是相當大的,板級信息都用C語言來實現。
而采用設備樹后,我們可以對多個SoC和板子使用同一個DT_MACHINE和板文件,板子和板子之間的差異更多只是通過不同的.dts文件來體現。
5、設備與驅動的匹配方式
使用設備樹后,驅動需要與在.dts中描述的設備節點進行匹配,從而使驅動的probe()函數執行。新的驅動、設備的匹配變成了設備樹節點的兼容屬性和設備驅動中的OF匹配表的匹配。(下篇看看OF是什么?)
6、設備的平臺數據屬性化
在Linux 2.6下,驅動習慣自定義platform_data,在arch/arm/mach-xxx注冊platform_device、i2c_board_info、spi_board_info等的時候綁定platform_data,而后驅動通過標準API獲取平臺數據。
譬如,在arch/arm/mach-at91/board-sam9263ek.c下用如下代碼注冊gpio_keys設備,它通過gpio_keys_platform_data結構體來定義platform_data。
static struct gpio_keys_button ek_buttons[] = {{ /* BP1, "leftclic" */.code = BTN_LEFT,.gpio = AT91_PIN_PC5,.active_low = 1,.desc = "left_click",.wakeup = 1,},{ /* BP2, "rightclic" */...}
};
static struct gpio_keys_platform_data ek_button_data = {.buttons = ek_buttons,.nbuttons = ARRAY_SIZE(ek_buttons),
};
static struct platform_device ek_button_device = {.name = "gpio-keys",.id = -1,.num_resources = 0,.dev = {.platform_data= &ek_button_data,}
};
設備驅動drivers/input/keyboard/gpio_keys.c則通過如下簡單方法取得這個信息。
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);...
}
在轉移到設備樹后,platform_data便不再喜歡放在arch/arm/mach-xxx中了,它需要從設備樹的屬性中獲取,比如一個電路板上有gpio_keys,則只需要在設備樹中添加類似arch/arm/boot/dts/exynos4210-origen.dts中的如代碼清單18.17所示的信息則可。
1 gpio_keys {2 compatible = "gpio-keys";3 #address-cells = <1>;4 #size-cells = <0>;56 up {7 label = "Up";8 gpios = <&gpx2 0 1>;9 linux,code = <KEY_UP>;
10 gpio-key,wakeup;
11 };
12
13 down {
14 label = "Down";
15 gpios = <&gpx2 1 1>;
16 linux,code = <KEY_DOWN>;
17 gpio-key,wakeup;
18 };
19 ...
20 };
而drivers/input/keyboard/gpio_keys.c則通過以of_開頭的讀屬性的API來讀取這些信息,并組織出gpio_keys_platform_data結構體,如代碼清單18.18所示。
代碼清單18.18 在GPIO按鍵驅動中獲取.dts中的鍵描述
1 static struct gpio_keys_platform_data *2 gpio_keys_get_devtree_pdata(struct device *dev)3 {4 struct device_node *node, *pp;5 struct gpio_keys_platform_data *pdata;6 struct gpio_keys_button *button;7 int error;8 int nbuttons;9 int i;
10
11 node = dev->of_node;
12 if (!node)
13 return ERR_PTR(-ENODEV);
14
15 nbuttons = of_get_child_count(node);
16 if (nbuttons == 0)
17 return ERR_PTR(-ENODEV);
18
19 pdata = devm_kzalloc(dev,
20 sizeof(*pdata) + nbuttons * sizeof(*button),
21 GFP_KERNEL);
22 if (!pdata)
23 return ERR_PTR(-ENOMEM);
24
25 pdata->buttons = (struct gpio_keys_button *)(pdata + 1);
26 pdata->nbuttons = nbuttons;
27
28 pdata->rep = !!of_get_property(node, "autorepeat", NULL);
29
30 i = 0;
31 for_each_child_of_node(node, pp) {
32 int gpio;
33 enum of_gpio_flags flags;
34
35 if (!of_find_property(pp, "gpios", NULL)) {
36 pdata->nbuttons--;
37 dev_warn(dev, "Found button without gpios\n");
38 continue;
39 }
40
41 gpio = of_get_gpio_flags(pp, 0, &flags);
42 if (gpio < 0) {
43 error = gpio;
44 if (error != -EPROBE_DEFER)
45 dev_err(dev,
46 "Failed to get gpio flags, error: %d\n",
47 error);
48 return ERR_PTR(error);
49 }
50
51 button = &pdata->buttons[i++];
52
53 button->gpio = gpio;
54 button->active_low = flags & OF_GPIO_ACTIVE_LOW;
55
56 if (of_property_read_u32(pp, "linux,code", &button->code)) {
57 dev_err(dev, "Button without keycode: 0x%x\n",
58 button->gpio);
59 return ERR_PTR(-EINVAL);
60 }
61
62 button->desc = of_get_property(pp, "label", NULL);
63
64 if (of_property_read_u32(pp, "linux,input-type", &button->type))
65 button->type = EV_KEY;
66
67 button->wakeup = !!of_get_property(pp, "gpio-key,wakeup", NULL);
68
69 if (of_property_read_u32(pp, "debounce-interval",
70 &button->debounce_interval))
71 button->debounce_interval = 5;
72 }
73
74 if (pdata->nbuttons == 0)
75 return ERR_PTR(-EINVAL);
76
77 return pdata;
78 }
上述代碼通過第31行的for_each_child_of_node()遍歷gpio_keys節點下的所有子節點,并通過of_get_gpio_flags()、of_property_read_u32()等API讀取出來與各個子節點對應的GPIO、與每個GPIO對應的鍵盤鍵值等。
18、站在Android看設備樹
最后再在android 的角度我們來看看這個東西。
1、概覽
設備樹 (DT) 是用于描述“不可發現”硬件的命名節點和屬性構成的一種數據結構。
操作系統(例如在 Android 中使用的 Linux 內核)會使用 DT 來支持 Android 設備使用的各種硬件配置。
硬件供應商會提供自己的 DT 源文件,接下來 Linux 會將這些文件編譯到引導加載程序使用的設備樹 Blob (DTB) 文件中。
設備樹疊加層 (DTO) 可讓主要的設備樹 Blob (DTB) 疊加在設備樹上。使用 DTO 的引導加載程序可以維護系統芯片 (SoC) DT,并動態疊加針對特定設備的 DT,從而向樹中添加節點并對現有樹中的屬性進行更改。
Android 9 版本中的更新
在 Android 9 中,在將統一設備樹 Blob 傳遞給內核之前,引導加載程序不得修改設備樹疊加層中定義的屬性。
2、加載設備樹
在引導加載程序中加載設備樹會涉及到構建、分區和運行。
圖 1. 在引導加載程序中加載設備樹的典型實現。
-
1、如需構建,請執行以下操作:
- 1、使用設備樹編譯器 (dtc) 將設備樹源 (.dts) 編譯成設備樹 blob (.dtb),將其格式設置為扁平化設備樹。
- 2、將 .dtb 文件刷寫到引導加載程序在運行時可訪問的位置
-
2、如需進行分區,請確定閃存中引導加載程序在運行時可訪問的可信位置以放置 .dtb。位置示例:
啟動分區
將 .dtb 放在啟動分區中,方法是將其附加到 image.gz,并作為“kernel”傳遞給 mkbootimg。
唯一分區
將 .dtb 放在唯一分區(例如 dtb 分區)中。
- 3、如需運行,請執行以下操作:
- 將 .dtb 從存儲空間加載到內存中。
- 啟動內核(已給定所加載 DT 的內存地址)。
白話一刻:
這個過程就是寫dts文件,然后編譯成dtb文件,然后loader會把dtb文件加載進內存,然后把這地址在kernel啟動的時候傳遞給kernel,kernel根據解析這個文件,實現硬件的配置。
3、術語
前面我們學習了dts,也知道這個dtc、dtb。這個dto是什么?
實現 DTO 包括分割設備樹、構建、分區和運行。
在實現可以正常工作之后,您還必須保持兩個 DT 之間的兼容性,并確定用于確保每個 DT 分區安全性的策略。
4、分割 DT
首先將設備樹分割成兩 (2) 部分:
- 主 DT。由 SoC 供應商提供的僅限 SoC 訪問的部分和默認配置。
- 疊加 DT。由原始設計制造商 (ODM)/原始設備制造商 (OEM) 提供的設備專用配置。
分割設備樹之后,您必須確保主 DT 和疊加 DT 之間的兼容性,以便通過合并主 DT 和疊加 DT 為設備生成完整的 DT。有關 DTO 格式和規則的詳細信息,請參閱 DTO 語法。如需詳細了解多個設備樹,請參閱多個 DT。(后面會寫)
5、構建主 DT 和疊加 DT
如需構建主 DT,請執行以下操作:
- 1、將主 DT .dts 編譯為 .dtb 文件。
- 2、將 .dtb 文件刷寫到引導加載程序在運行時可訪問的分區(詳見下文)。
如需構建疊加 DT,請執行以下操作:
- 1、將疊加 DT .dts 編譯為 .dtbo 文件。雖然此文件格式與格式設為扁平化設備樹的 .dtb 文件相同,但是用不同的文件擴展名可以將其與主 DT 區分開來。
- 2、將 .dtbo 文件刷寫到引導加載程序在運行時可訪問的分區(詳見下文)。
6、對 DT 進行分區
在閃存中確定引導加載程序在運行時可訪問和可信的位置信息以放入 .dtb 和 .dtbo。
主 DT 的示例位置:
- 啟動分區的一部分,已附加到內核 (image.gz)。
- 單獨的 DT blob (.dtb),位于專用分區 (dtb)。
疊加 DT 的示例位置:
注意:疊加 DT 分區的大小取決于設備和主 DT blob 上所需的更改量。通常,8 MB 已足夠當前使用并已為未來擴展留出了空間(如果需要的話)。
7、在引導加載程序中運行
8、保持兼容性
主 DTB(來自 SoC 供應商)會被視為 DTBO 的 API 表面。將設備樹分離為 SoC 通用部件和設備專用部件后,您必須確保這兩個部件以后相互兼容,包括:
- 主 DT 中的 DT 定義(例如,節點、屬性、標簽)。主 DT 中的任何定義更改都可能會觸發疊加 DT 中的更改。例如,如需更正主 DT 中的某個節點名稱,請定義映射到原始節點名稱的“別名”標簽(以免更改疊加 DT)。
- 疊加 DT 的存儲位置(例如,分區名稱、存儲格式)。
9、確保安全
引導加載程序必須確保 DTB/DTBO 安全無虞、未被修改且未被損壞。您可以使用任何解決方案來保護 DTB/DTBO,例如,VBoot 1.0 中的啟動映像簽名或 AVB 哈希頁腳 (VBoot 2.0)。
- 如果 DTB/DTBO 位于專屬的分區中,您可以將該分區添加到 AVB 的信任鏈。信任鏈從硬件保護的信任根開始,并進入引導加載程序,從而驗證 DTB/DTBO 分區的完整性和真實性。
- 如果 DTB/DTBO 位于現有分區(如 odm 分區)中,該分區應位于 AVB 的信任鏈中。(DTBO 分區可以與 odm 分區共享一個公鑰)。
10、小結-白話一刻
疊加 DT文件生成的dtbo格式與格式設為扁平化設備樹的 .dtb 文件相同,但是用不同的文件擴展名可以將其與主 DT 區分開來。
到這里就差不多關于設備樹的東西,沒想到寫了這么多。
85%的內容都來自于宋老師的書籍《Linux設備驅動開發詳解》!
看到這里的你也辛苦啦!記得點贊哦!
下次聊!