1)實驗平臺:正點原子Linux開發板
2)
摘自《正點原子I.MX6U嵌入式Linux驅動開發指南》前面章節中我們多次提到“設備樹”這個概念,因為時機未到,所以當時並沒有詳細的講解什麼是“設備樹”,本章我們就來詳細的談一談設備樹。掌握設備樹是Linux驅動開發人員必備的技能!因為在新版本的Linux中,ARM相關的驅動全部採用了設備樹(也有支持老式驅動的,比較少),最新出的CPU其驅動開發也基本都是基於設備樹的,比如ST新出的STM32MP157、NXP的I.MX8系列等。我們所使用的Linux版本為4.1.15,其支持設備樹,所以正點原子I.MX6U-ALPHA開發板的所有Linux驅動都是基於設備樹的。本章我們就來了解一下設備樹的起源、重點學習一下設備樹語法。
43.1 什麼是設備樹?
設備樹(Device Tree),將這個詞分開就是“設備”和“樹”,描述設備樹的文件叫做DTS(Device Tree Source),這個DTS文件採用樹形結構描述板級設備,也就是開發板上的設備信息,比如CPU數量、內存基地址、IIC接口上接了哪些設備、SPI接口上接了哪些設備等等,如圖43.1.1所示:
圖43.1.1 設備樹結構示意圖
在圖43.1.1中,樹的主幹就是系統總線,IIC控制器、GPIO控制器、SPI控制器等都是接到系統主線上的分支。IIC控制器有分為IIC1和IIC2兩種,其中IIC1上接了FT5206和AT24C02這兩個IIC設備,IIC2上只接了MPU6050這個設備。DTS文件的主要功能就是按照圖43.1.1所示的結構來描述板子上的設備信息,DTS文件描述設備信息是有相應的語法規則要求的,稍後我們會詳細的講解DTS語法規則。
在3.x版本(具體哪個版本筆者也無從考證)以前的Linux內核中ARM架構並沒有採用設備樹。在沒有設備樹的時候Linux是如何描述ARM架構中的板級信息呢?在Linux內核源碼中大量的arch/arm/mach-xxx和arch/arm/plat-xxx文件夾,這些文件夾裡面的文件就是對應平臺下的板級信息。比如在arch/arm/mach-smdk2440.c中有如下內容(有縮減):
示例代碼43.1.1 mach-smdk2440.c文件代碼段
90staticstruct s3c2410fb_display smdk2440_lcd_cfg __initdata ={
91
92.lcdcon5 = S3C2410_LCDCON5_FRM565
|93 S3C2410_LCDCON5_INVVLINE |
94 S3C2410_LCDCON5_INVVFRAME |
95 S3C2410_LCDCON5_PWREN |
96 S3C2410_LCDCON5_HWSWP,
......
113};
114
115staticstruct s3c2410fb_mach_info smdk2440_fb_info __initdata ={
116.displays =&smdk2440_lcd_cfg,
117.num_displays =1,
118
.default_display =0,......
133};
134
135staticstruct platform_device *smdk2440_devices[] __initdata ={
136 &s3c_device_ohci,
137&s3c_device_lcd,
138&s3c_device_wdt,
139&s3c_device_i2c0,
140
&s3c_device_iis,141};
上述代碼中的結構體變量smdk2440_fb_info就是描述SMDK2440這個開發板上的LCD信息的,結構體指針數組smdk2440_devices描述的SMDK2440這個開發板上的所有平臺相關信息。這個僅僅是使用2440這個芯片的SMDK2440開發板下的LCD信息,SMDK2440開發板還有很多的其他外設硬件和平臺硬件信息。使用2440這個芯片的板子有很多,每個板子都有描述相應板級信息的文件,這僅僅只是一個2440。隨著智能手機的發展,每年新出的ARM架構芯片少說都在數十、數百款,Linux內核下板級信息文件將會成指數級增長!這些板級信息文件都是.c或.h文件,都會被硬編碼進Linux內核中,導致Linux內核“虛胖”。就好比你喜歡吃自助餐,然後花了100多到一家宣傳看著很不錯的自助餐廳,結果你想吃的牛排、海鮮、烤肉基本沒多少,全都是一些涼菜、炒麵、西瓜、飲料等小吃,相信你此時肯定會脫口而出一句“F*k!”、“騙子!”。同樣的,當Linux之父linus看到ARM社區向Linux內核添加了大量“無用”、冗餘的板級信息文件,不禁的發出了一句“This whole ARM thing is a f*cking pain in the ass”。從此以後ARM社區就引入了PowerPC等架構已經採用的設備樹(Flattened Device Tree),將這些描述板級硬件信息的內容都從Linux內中分離開來,用一個專屬的文件格式來描述,這個專屬的文件就叫做設備樹,文件擴展名為.dts。一個SOC可以作出很多不同的板子,這些不同的板子肯定是有共同的信息,將這些共同的信息提取出來作為一個通用的文件,其他的.dts文件直接引用這個通用文件即可,這個通用文件就是.dtsi文件,類似於C語言中的頭文件。一般.dts描述板級信息(也就是開發板上有哪些IIC設備、SPI設備等),.dtsi描述SOC級信息(也就是SOC有幾個CPU、主頻是多少、各個外設控制器信息等)。
這個就是設備樹的由來,簡而言之就是,Linux內核中ARM架構下有太多的冗餘的垃圾板級信息文件,導致linus震怒,然後ARM社區引入了設備樹。
43.2 DTS、DTB和DTC
上一小節說了,設備樹源文件擴展名為.dts,但是我們在前面移植Linux的時候卻一直在使用.dtb文件,那麼DTS和DTB這兩個文件是什麼關係呢?DTS是設備樹源碼文件,DTB是將DTS編譯以後得到的二進制文件。將.c文件編譯為.o需要用到gcc編譯器,那麼將.dts編譯為.dtb需要什麼工具呢?需要用到DTC工具!DTC工具源碼在Linux內核的scripts/dtc目錄下,scripts/dtc/Makefile文件內容如下:
示例代碼43.2.1>
1 hostprogs-y := dtc
2 always :=$(hostprogs-y)
3
4 dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o \\
5 srcpos.o checks.o util.o
6 dtc-objs += dtc-lexer.lex.o dtc-parser.tab.o
......
可以看出,DTC工具依賴於dtc.c、flattree.c、fstree.c等文件,最終編譯並鏈接出DTC這個主機文件。如果要編譯DTS文件的話只需要進入到Linux源碼根目錄下,然後執行如下命令:
makeall
或者:
makedtbs
“makeall”命令是編譯Linux源碼中的所有東西,包括zImage,.ko驅動模塊以及設備樹,如果只是編譯設備樹的話建議使用“makedtbs”命令。
基於ARM架構的SOC有很多種,一種SOC又可以製作出很多款板子,每個板子都有一個對應的DTS文件,那麼如何確定編譯哪一個DTS文件呢?我們就以I.MX6ULL這款芯片對應的板子為例來看一下,打開arch/arm/boot/dts/Makefile,有如下內容:
示例代碼43.2.2 arch/arm/boot/dts/Makefile文件代碼段
381 dtb-$(CONFIG_SOC_IMX6UL) += \\
382 imx6ul-14x14-ddr3-arm2.dtb \\
383 imx6ul-14x14-ddr3-arm2-emmc.dtb \\
......
400 dtb-$(CONFIG_SOC_IMX6ULL) += \\
401 imx6ull-14x14-ddr3-arm2.dtb \\
402 imx6ull-14x14-ddr3-arm2-adc.dtb \\
403 imx6ull-14x14-ddr3-arm2-cs42888.dtb \\
404 imx6ull-14x14-ddr3-arm2-ecspi.dtb \\
405 imx6ull-14x14-ddr3-arm2-emmc.dtb \\
406 imx6ull-14x14-ddr3-arm2-epdc.dtb \\
407 imx6ull-14x14-ddr3-arm2-flexcan2.dtb \\
408 imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb \\
409 imx6ull-14x14-ddr3-arm2-lcdif.dtb \\
410 imx6ull-14x14-ddr3-arm2-ldo.dtb \\
411 imx6ull-14x14-ddr3-arm2-qspi.dtb \\
412 imx6ull-14x14-ddr3-arm2-qspi-all.dtb \\
413 imx6ull-14x14-ddr3-arm2-tsc.dtb \\
414 imx6ull-14x14-ddr3-arm2-uart2.dtb \\
415 imx6ull-14x14-ddr3-arm2-usb.dtb \\
416 imx6ull-14x14-ddr3-arm2-wm8958.dtb \\
417 imx6ull-14x14-evk.dtb \\
418 imx6ull-14x14-evk-btwifi.dtb \\
419 imx6ull-14x14-evk-emmc.dtb \\
420 imx6ull-14x14-evk-gpmi-weim.dtb \\
421 imx6ull-14x14-evk-usb-certi.dtb \\
422 imx6ull-alientek-emmc.dtb \\
423 imx6ull-alientek-nand.dtb \\
424 imx6ull-9x9-evk.dtb \\
425 imx6ull-9x9-evk-btwifi.dtb \\
426 imx6ull-9x9-evk-ldo.dtb
427 dtb-$(CONFIG_SOC_IMX6SLL) += \\
428 imx6sll-lpddr2-arm2.dtb \\
429 imx6sll-lpddr3-arm2.dtb \\
......
可以看出,當選中I.MX6ULL這個SOC以後(CONFIG_SOC_IMX6ULL=y),所有使用到I.MX6ULL這個SOC的板子對應的.dts文件都會被編譯為.dtb。如果我們使用I.MX6ULL新做了一個板子,只需要新建一個此板子對應的.dts文件,然後將對應的.dtb文件名添加到dtb-$(CONFIG_SOC_IMX6ULL)下,這樣在編譯設備樹的時候就會將對應的.dts編譯為二進制的.dtb文件。
示例代碼43.2.2中第422和423行就是我們在給正點原子的I.MX6U-ALPHA開發板移植Linux系統的時候添加的設備樹。關於.dtb文件怎麼使用這裡就不多說了,前面講解Uboot移植、Linux內核移植的時候已經無數次的提到如何使用.dtb文件了(uboot中使用bootz或bootm命令向Linux內核傳遞二進制設備樹文件(.dtb))。
43.3 DTS語法
雖然我們基本上不會從頭到尾重寫一個.dts文件,大多時候是直接在SOC廠商提供的.dts文件上進行修改。但是DTS文件語法我們還是需要詳細的學習一遍,因為我們肯定需要修改.dts文件。大家不要看到要學習新的語法就覺得會很複雜,DTS語法非常的人性化,是一種ASCII文本文件,不管是閱讀還是修改都很方便。
本節我們就以imx6ull-alientek-emmc.dts這個文件為例來講解一下DTS語法。關於設備樹詳細的語法規則請參考《Devicetree SpecificationV0.2.pdf》和《Power_ePAPR_APPROVED_v1.12.pdf》這兩份文檔,此兩份文檔已經放到了開發板光盤中,路徑為:4、參考資料->Devicetree SpecificationV0.2.pdf、4、參考資料->Power_ePAPR_APPROVED_v1.12.pdf
43.3.1 .dtsi頭文件
和C語言一樣,設備樹也支持頭文件,設備樹的頭文件擴展名為.dtsi。在imx6ull-alientek-emmc.dts中有如下所示內容:
示例代碼43.3.1.1 imx6ull-alientek-emmc.dts文件代碼段
12 #include <dt-bindings/input/input.h>
13 #include "imx6ull.dtsi"
第12行,使用“#include”來引用“input.h”這個.h頭文件。
第13行,使用“#include”來引用“imx6ull.dtsi”這個.dtsi頭文件。
看到這裡,大家可能會疑惑,不是說設備樹的擴展名是.dtsi嗎?為什麼也可以直接引用C語言中的.h頭文件呢?這裡並沒有錯,.dts文件引用C語言中的.h文件,甚至也可以引用.dts文件,打開imx6ull-14x14-evk-gpmi-weim.dts這個文件,此文件中有如下內容:
示例代碼43.3.1.2 imx6ull-14x14-evk-gpmi-weim.dts文件代碼段
9 #include "imx6ull-14x14-evk.dts"
可以看出,示例代碼43.3.1.2中直接引用了.dts文件,因此在.dts設備樹文件中,可以通過“#include”來引用.h、.dtsi和.dts文件。只是,我們在編寫設備樹頭文件的時候最好選擇.dtsi後綴。
一般.dtsi文件用於描述SOC的內部外設信息,比如CPU架構、主頻、外設寄存器地址範圍,比如UART、IIC等等。比如imx6ull.dtsi就是描述I.MX6ULL這顆SOC內部外設情況信息的,內容如下:
示例代碼43.3.1.3 imx6ull.dtsi文件代碼段
10 #include <dt-bindings/clock/imx6ul-clock.h>
11 #include <dt-bindings/gpio/gpio
.h>12 #include <dt-bindings/interrupt-controller/arm-gic.h>
13 #include "imx6ull-pinfunc.h"
14 #include "imx6ull-pinfunc-snvs.h"
15 #include "skeleton.dtsi"
16
17/{
18 aliases {
19 can0 =&flexcan1;
......
48
};49
50 cpus {
51 #address-cells =<1>;
52 #size-cells =<0>;
53
54 cpu0: cpu@0 {
55 compatible ="arm,cortex-a7";
56 device_type ="cpu";
......
89
};90};
91
92 intc: interrupt-controller@00a01000 {
93 compatible ="arm,cortex-a7-gic";
94 #interrupt-cells =<3>;
95 interrupt-controller;
96 reg =<0x00a010000x1000>,
97<0x00a020000x100>;
98
};99
100 clocks {
101 #address-cells =<1>;
102 #size-cells =<0>;
103
104 ckil: clock@0 {
105 compatible ="fixed-clock";
106 reg =<0>;
107 #clock-cells =<0
>;108 clock-frequency =<32768>;
109 clock-output-names ="ckil";
110};
......
135};
136
137 soc {
138 #address-cells =<1>;
139 #size-cells
=<1>;140 compatible ="simple-bus";
141 interrupt-parent =gpc>;
142 ranges;
143
144 busfreq {
145 compatible ="fsl,imx_busfreq";
......
162};
197
198 gpmi: gpmi-nand@01806000
{199 compatible ="fsl,imx6ull-gpmi-nand","fsl, imx6ul-gpmi-nand";
200 #address-cells =<1>;
201 #size-cells =<1>;
202 reg =<0x018060000x2000>,<0x018080000x4000>;
......
216};
......
1177};
1178};
示例代碼43.3.1.3中第54~89行就是cpu0這個設備節點信息,這個節點信息描述了I.MX6ULL這顆SOC所使用的CPU信息,比如架構是cortex-A7,頻率支持996MHz、792MHz、528MHz、396MHz和198MHz等等。在imx6ull.dtsi文件中不僅僅描述了cpu0這一個節點信息,I.MX6ULL這顆SOC所有的外設都描述的清清楚楚,比如ecspi1~4、uart1~8、usbphy1~2、i2c1~4等等,關於這些設備節點信息的具體內容我們稍後在詳細的講解。
43.3.2設備節點
設備樹是採用樹形結構來描述板子上的設備信息的文件,每個設備都是一個節點,叫做設備節點,每個節點都通過一些屬性信息來描述節點信息,屬性就是鍵—值對。以下是從imx6ull.dtsi文件中縮減出來的設備樹文件內容:
示例代碼43.3.2.1 設備樹模板
1/{
2 aliases {
3 can0 =&flexcan1;
4 };
5
6 cpus {
7 #address-cells =<1>;
8 #size-cells =<0>;
9
10 cpu0: cpu@0 {
11 compatible ="arm,cortex-a7";
12 device_type ="cpu";
13 reg =<0
>;14 };
15 };
16
17 intc: interrupt-controller@00a01000 {
18 compatible ="arm,cortex-a7-gic";
19 #interrupt-cells =<3>;
20 interrupt-controller;
21 reg =<0x00a010000x1000>,
22 <0x00a020000x100
>;23 };
24}
第1行,“/”是根節點,每個設備樹文件只有一個根節點。細心的同學應該會發現,imx6ull.dtsi和imx6ull-alientek-emmc.dts這兩個文件都有一個“/”根節點,這樣不會出錯嗎?不會的,因為這兩個“/”根節點的內容會合併成一個根節點。
第2、6和17行,aliases、cpus和intc是三個子節點,在設備樹中節點命名格式如下:
node-name@unit-address
其中“node-name”是節點名字,為ASCII字符串,節點名字應該能夠清晰的描述出節點的功能,比如“uart1”就表示這個節點是UART1外設。“unit-address”一般表示設備的地址或寄存器首地址,如果某個節點沒有地址或者寄存器的話“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。
但是我們在示例代碼43.3.2.1中我們看到的節點命名卻如下所示:
cpu0:cpu@0
上述命令並不是“node-name@unit-address”這樣的格式,而是用“:”隔開成了兩部分,“:”前面的是節點標籤(label),“:”後面的才是節點名字,格式如下所示:
label: node-name@unit-address
引入label的目的就是為了方便訪問節點,可以直接通過&label來訪問這個節點,比如通過&cpu0就可以訪問“cpu@0”這個節點,而不需要輸入完整的節點名字。再比如節點“intc: interrupt-controller@00a01000”,節點label是intc,而節點名字就很長了,為“interrupt-controller@00a01000”。很明顯通過&intc來訪問“interrupt-controller@00a01000”這個節點要方便很多!
第10行,cpu0也是一個節點,只是cpu0是cpus的子節點。
每個節點都有不同屬性,不同的屬性又有不同的內容,屬性都是鍵值對,值可以為空或任意的字節流。設備樹源碼中常用的幾種數據形式如下所示:
①、字符串
compatible = "arm,cortex-a7";
上述代碼設置compatible屬性的值為字符串“arm,cortex-a7”。
②、32位無符號整數
reg = <0>;
上述代碼設置reg屬性的值為0,reg的值也可以設置為一組值,比如:
reg = <0 0x123456 100>;
③、字符串列表
屬性值也可以為字符串列表,字符串和字符串之間採用“,”隔開,如下所示:
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";
上述代碼設置屬性compatible的值為“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。
43.3.3 標準屬性
節點是由一堆的屬性組成,節點都是具體的設備,不同的設備需要的屬性不同,用戶可以自定義屬性。除了用戶自定義屬性,有很多屬性是標準屬性,Linux下的很多外設驅動都會使用這些標準屬性,本節我們就來學習一下幾個常用的標準屬性。
1、compatible屬性
compatible屬性也叫做“兼容性”屬性,這是非常重要的一個屬性!compatible屬性的值是一個字符串列表,compatible屬性用於將設備和驅動綁定起來。字符串列表用於選擇設備所要使用的驅動程序,compatible屬性的值格式如下所示:
"manufacturer,model"
其中manufacturer表示廠商,model一般是模塊對應的驅動名字。比如imx6ull-alientek-emmc.dts中sound節點是I.MX6U-ALPHA開發板的音頻設備節點,I.MX6U-ALPHA開發板上的音頻芯片採用的歐勝(WOLFSON)出品的WM8960,sound節點的compatible屬性值如下:
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
屬性值有兩個,分別為“fsl,imx6ul-evk-wm8960”和“fsl,imx-audio-wm8960”,其中“fsl”表示廠商是飛思卡爾,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驅動模塊名字。sound這個設備首先使用第一個兼容值在Linux內核裡面查找,看看能不能找到與之匹配的驅動文件,如果沒有找到的話就使用第二個兼容值查找,直到找到或者查找完整個Linux內核也沒有找到對應的驅動。
一般驅動程序文件都會有一個OF匹配表,此OF匹配表保存著一些compatible值,如果設備節點的compatible屬性值和OF匹配表中的任何一個值相等,那麼就表示設備可以使用這個驅動。比如在文件imx-wm8960.c中有如下內容:
示例代碼43.3.3.1 imx-wm8960.c文件代碼段
632staticconststruct of_device_id imx_wm8960_dt_ids[]={
633{.compatible ="fsl,imx-audio-wm8960",},
634{/* sentinel */}
635};
636 MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
637
638staticstruct platform_driver imx_wm8960_driver ={
639.driver ={
640.name ="imx-wm8960",
641.pm =&snd_soc_pm_ops,
642.of_match_table = imx_wm8960_dt_ids,
643},
644. probe = imx_wm8960_probe,
645.remove = imx_wm8960_remove,
646};
第632~635行的數組imx_wm8960_dt_ids就是imx-wm8960.c這個驅動文件的匹配表,此匹配表只有一個匹配值“fsl,imx-audio-wm8960”。如果在設備樹中有哪個節點的compatible屬性值與此相等,那麼這個節點就會使用此驅動文件。
第642行,wm8960採用了platform_driver驅動模式,關於platform_driver驅動後面會講解。此行設置.of_match_table為imx_wm8960_dt_ids,也就是設置這個platform_driver所使用的OF匹配表。
2、model屬性
model屬性值也是一個字符串,一般model屬性描述設備模塊信息,比如名字什麼的,比如:
model = "wm8960-audio";
3、status屬性
status屬性看名字就知道是和設備狀態有關的,status屬性值也是字符串,字符串是設備的狀態信息,可選的狀態如表43.3.3.1所示:
表43.3.3.1 status屬性值表
4、#address-cells和#size-cells屬性
這兩個屬性的值都是無符號32位整形,#address-cells和#size-cells這兩個屬性可以用在任何擁有子節點的設備中,用於描述子節點的地址信息。#address-cells屬性值決定了子節點reg屬性中地址信息所佔用的字長(32位),#size-cells屬性值決定了子節點reg屬性中長度信息所佔的字長(32位)。#address-cells和#size-cells表明了子節點應該如何編寫reg屬性值,一般reg屬性都是和地址有關的內容,和地址相關的信息有兩種:起始地址和地址長度,reg屬性的格式一為:
reg = <address1>
每個“addresslength”組合表示一個地址範圍,其中address是起始地址,length是地址長度,#address-cells表明address這個數據所佔用的字長,#size-cells表明length這個數據所佔用的字長,比如:
示例代碼43.3.3.2 #address-cells和#size-cells屬性
1 spi4 {
2 compatible ="spi-gpio";
3 #address-cells =<1>;
4 #size-cells =<0>;
5
6 gpio_spi: gpio_spi@0 {
7 compatible ="fairchild,74hc595";
8 reg =<0>;
9
};10};
11
12 aips3: aips-bus@02200000 {
13 compatible ="fsl,aips-bus","simple-bus";
14 #address-cells =<1>;
15 #size-cells =<1>;
16
17 dcp: dcp@02280000 {
18 compatible ="fsl,imx6sl-dcp";
19 reg =<0x022800000x4000>;
20 };
21};
第2,3行,節點spi4的#address-cells = <1>,#size-cells = <0>,說明spi4的子節點reg屬性中起始地址所佔用的字長為1,地址長度所佔用的字長為0。
第8行,子節點gpio_spi: gpio_spi@0的reg 屬性值為<0>,因為父節點設置了#address-cells = <1>,#size-cells = <0>,因此addres=0,沒有length的值,相當於設置了起始地址,而沒有設置地址長度。
第14,15行,設置aips3: aips-bus@02200000節點#address-cells = <1>,#size-cells = <1>,說明aips3: aips-bus@02200000節點起始地址長度所佔用的字長為1,地址長度所佔用的字長也為1。
第19行,子節點dcp: dcp@02280000的reg屬性值為<0x02280000 0x4000>,因為父節點設置了#address-cells = <1>,#size-cells = <1>,address= 0x02280000,length= 0x4000,相當於設置了起始地址為0x02280000,地址長度為0x40000。
5、reg屬性
reg屬性前面已經提到過了,reg屬性的值一般是(address,length)對。reg屬性一般用於描述設備地址空間資源信息,一般都是某個外設的寄存器地址範圍信息,比如在imx6ull.dtsi中有如下內容:
示例代碼43.3.3.3 uart1節點信息
323 uart1: serial@02020000 {
324 compatible ="fsl,imx6ul-uart",
325"fsl,imx6q-uart","fsl,imx21-uart";
326 reg =<0x020200000x4000>;
327 interrupts =<GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
328 clocks =clks IMX6UL_CLK_UART1_IPG>,
329clks IMX6UL_CLK_UART1_SERIAL>;
330 clock-names ="ipg","per";
331 status ="disabled";
332};
上述代碼是節點uart1,uart1節點描述了I.MX6ULL的UART1相關信息,重點是第326行的reg屬性。其中uart1的父節點aips1: aips-bus@02000000設置了#address-cells = <1>、#size-cells = <1>,因此reg屬性中address=0x02020000,length=0x4000。查閱《I.MX6ULL參考手冊》可知,I.MX6ULL的UART1寄存器首地址為0x02020000,但是UART1的地址長度(範圍)並沒有0x4000這麼多,這裡我們重點是獲取UART1寄存器首地址。
6、ranges屬性
ranges屬性值可以為空或者按照(child-bus-address,parent-bus-address,length)格式編寫的數字矩陣,ranges是一個地址映射/轉換表,ranges屬性每個項目由子地址、父地址和地址空間長度這三部分組成:
child-bus-address:子總線地址空間的物理地址,由父節點的#address-cells確定此物理地址所佔用的字長。
parent-bus-address:父總線地址空間的物理地址,同樣由父節點的#address-cells確定此物理地址所佔用的字長。
length: 子地址空間的長度,由父節點的#size-cells確定此地址長度所佔用的字長。
如果ranges屬性值為空值,說明子地址空間和父地址空間完全相同,不需要進行地址轉換,對於我們所使用的I.MX6ULL來說,子地址空間和父地址空間完全相同,因此會在imx6ull.dtsi中找到大量的值為空的ranges屬性,如下所示:
示例代碼43.3.3.4 imx6ull.dtsi文件代碼段
137 soc {
138 #address-cells =<1>;
139 #size-cells =<1>;
140 compatible ="simple-bus";
141 interrupt-parent =gpc>;
142 ranges;
......
1177}
第142行定義了ranges屬性,但是ranges屬性值為空。
ranges屬性不為空的示例代碼如下所示:
示例代碼43.3.3.5 ranges屬性不為空
1 soc {
2 compatible ="simple-bus";
3 #address-cells =<1>;
4 #size-cells =<1>;
5 ranges =<0x00xe00000000x00100000>;
6
7 serial {
8 device_type ="serial";
9 compatible ="ns16550";
10 reg =<0x46000x100>;
11 clock-frequency =<0>;
12 interrupts =<0xA0x8>;
13 interrupt-parent =ipic>;
14 };
15};
第5行,節點soc定義的ranges屬性,值為<0x0 0xe0000000 0x00100000>,此屬性值指定了一個1024KB(0x00100000)的地址範圍,子地址空間的物理起始地址為0x0,父地址空間的物理起始地址為0xe0000000。
第6行,serial是串口設備節點,reg屬性定義了serial設備寄存器的起始地址為0x4600,寄存器長度為0x100。經過地址轉換,serial設備可以從0xe0004600開始進行讀寫操作,0xe0004600=0x4600+0xe0000000。
7、name屬性
name屬性值為字符串,name屬性用於記錄節點名字,name屬性已經被棄用,不推薦使用name屬性,一些老的設備樹文件可能會使用此屬性。
8、device_type屬性
device_type屬性值為字符串,IEEE 1275會用到此屬性,用於描述設備的FCode,但是設備樹沒有FCode,所以此屬性也被拋棄了。此屬性只能用於cpu節點或者memory節點。imx6ull.dtsi的cpu0節點用到了此屬性,內容如下所示:
示例代碼43.3.3.6 imx6ull.dtsi文件代碼段
54 cpu0
: cpu@0 {55 compatible ="arm,cortex-a7";
56 device_type ="cpu";
57 reg =<0>;
......
89};
關於標準屬性就講解這麼多,其他的比如中斷、IIC、SPI等使用的標準屬性等到具體的例程在講解。
43.3.4 根節點compatible屬性
每個節點都有compatible屬性,根節點“/”也不例外,imx6ull-alientek-emmc.dts文件中根節點的compatible屬性內容如下所示:
示例代碼43.3.4.1 imx6ull-alientek-emmc.dts根節點compatible屬性
14/{
15 model ="Freescale i.MX6 ULL 14x14 EVK Board";
16 compatible ="fsl,imx6ull-14x14-evk","fsl,imx6ull";
......
148}
可以看出,compatible有兩個值:“fsl,imx6ull-14x14-evk”和“fsl,imx6ull”。前面我們說了,設備節點的compatible屬性值是為了匹配Linux內核中的驅動程序,那麼根節點中的compatible屬性是為了做什麼工作的?通過根節點的compatible屬性可以知道我們所使用的設備,一般第一個值描述了所使用的硬件設備名字,比如這裡使用的是“imx6ull-14x14-evk”這個設備,第二個值描述了設備所使用的SOC,比如這裡使用的是“imx6ull”這顆SOC。Linux內核會通過根節點的compoatible屬性查看是否支持此設備,如果支持的話設備就會啟動Linux內核。接下來我們就來學習一下Linux內核在使用設備樹前後是如何判斷是否支持某款設備的。
1、使用設備樹之前設備匹配方法
在沒有使用設備樹以前,uboot會向Linux內核傳遞一個叫做machine id的值,machine id也就是設備ID,告訴Linux內核自己是個什麼設備,看看Linux內核是否支持。Linux內核是支持很多設備的,針對每一個設備(板子),Linux內核都用MACHINE_START和MACHINE_END來定義一個machine_desc結構體來描述這個設備,比如在文件arch/arm/mach-imx/mach-mx35_3ds.c中有如下定義:
示例代碼43.3.4.2 MX35_3DS設備
613 MACHINE_START(MX35_3DS,"Freescale MX35PDK")
614/* Maintainer: Freescale Semiconductor, Inc */
615.atag_offset =0x100,
616.map_io = mx35_map_io,
617.init_early = imx35_init_early,
618.init_irq = mx35_init_irq,
619.init_time = mx35pdk_timer_init,
620.init_machine = mx35_3ds_init,
621.reserve = mx35_3ds_reserve,
622.restart = mxc_restart,
623 MACHINE_END
上述代碼就是定義了“Freescale MX35PDK”這個設備,其中MACHINE_START和MACHINE_END定義在文件arch/arm/include/asm/mach/arch.h中,內容如下:
示例代碼43.3.4.3 MACHINE_START和MACHINE_END宏定義
#define MACHINE_START(_type,_name) \\
static const struct machine_desc __mach_desc_##_type \\
__used \\
__attribute__((__section__(".arch.info.init"))) = { \\
.nr = MACH_TYPE_##_type, \\
.name = _name,
#define MACHINE_END \\
};
根據MACHINE_START和MACHINE_END的宏定義,將示例代碼43.3.4.2展開後如下所示:
示例代碼43.3.4.3 展開以後
1staticconststruct machine_desc __mach_desc_MX35_3DS \\
2 __used \\
3 __attribute__((__section__(".arch.info.init")))={
4 .nr = MACH_TYPE_MX35_3DS,
5 .name ="Freescale MX35PDK",
6 /* Maintainer: Freescale Semiconductor, Inc */
7 .atag_offset =0x100,
8 .map_io = mx35_map_io,
9 .init_early = imx35_init_early,
10 .init_irq = mx35_init_irq,
11 .init_time = mx35pdk_timer_init,
12 .init_machine = mx35_3ds_init,
13 . reserve = mx35_3ds_reserve,
14 .restart = mxc_restart,
15}
從示例代碼43.3.4.3中可以看出,這裡定義了一個machine_desc類型的結構體變量__mach_desc_MX35_3DS,這個變量存儲在“.arch.info.init”段中。第4行的MACH_TYPE_MX35_3DS就是“Freescale MX35PDK”這個板子的machineid。MACH_TYPE_MX35_3DS定義在文件include/generated/mach-types.h中,此文件定義了大量的machineid,內容如下所示:
示例代碼43.3.4.3 mach-types.h文件中的machine id
15 #define MACH_TYPE_EBSA110 0
16 #define MACH_TYPE_RISCPC 1
17 #define MACH_TYPE_EBSA285 4
18 #define MACH_TYPE_NETWINDER 5
19 #define MACH_TYPE_CATS 6
20 #define MACH_TYPE_SHARK 15
21 #define MACH_TYPE_BRUTUS 16
22 #define MACH_TYPE_PERSONAL_SERVER 17
......
287 #define MACH_TYPE_MX35_3DS 1645
......
1000 #define MACH_TYPE_PFLA03 4575
第287行就是MACH_TYPE_MX35_3DS的值,為1645。
前面說了,uboot會給Linux內核傳遞machineid這個參數,Linux內核會檢查這個machineid,其實就是將machineid與示例代碼43.3.4.3中的這些MACH_TYPE_XXX宏進行對比,看看有沒有相等的,如果相等的話就表示Linux內核支持這個設備,如果不支持的話那麼這個設備就沒法啟動Linux內核。
2、使用設備樹以後的設備匹配方法
當Linux內核引入設備樹以後就不再使用MACHINE_START了,而是換為了DT_MACHINE_START。DT_MACHINE_START也定義在文件arch/arm/include/asm/mach/arch.h裡面,定義如下:
示例代碼43.3.4.4 DT_MACHINE_START宏
#define DT_MACHINE_START(_name, _namestr) \\
static const struct machine_desc __mach_desc_##_name \\
__used \\
__attribute__((__section__(".arch.info.init"))) = { \\
.nr = ~0, \\
.name = _namestr,
可以看出,DT_MACHINE_START和MACHINE_START基本相同,只是.nr的設置不同,在DT_MACHINE_START裡面直接將.nr設置為~0。說明引入設備樹以後不會再根據machineid來檢查Linux內核是否支持某個設備了。
打開文件arch/arm/mach-imx/mach-imx6ul.c,有如下所示內容:
示例代碼43.3.4.5 imx6ull設備
208staticconstchar*imx6ul_dt_compat[] __initconst ={
209"fsl,imx6ul",
210"fsl,imx6ull",
211NULL,
212};
213
214 DT_MACHINE_START(IMX6UL,"Freescale i.MX6 Ultralite (Device Tree)")
215.map_io = imx6ul_map_io,
216. init_irq = imx6ul_init_irq,
217.init_machine = imx6ul_init_machine,
218.init_late = imx6ul_init_late,
219.dt_compat = imx6ul_dt_compat,
220 MACHINE_END
machine_desc結構體中有個.dt_compat成員變量,此成員變量保存著本設備兼容屬性,示例代碼43.3.4.5中設置.dt_compat = imx6ul_dt_compat,imx6ul_dt_compat表裡面有"fsl,imx6ul"和"fsl,imx6ull"這兩個兼容值。只要某個設備(板子)根節點“/”的compatible屬性值與imx6ul_dt_compat表中的任何一個值相等,那麼就表示Linux內核支持此設備。imx6ull-alientek-emmc.dts中根節點的compatible屬性值如下:
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
其中“fsl,imx6ull”與imx6ul_dt_compat中的“fsl,imx6ull”匹配,因此I.MX6U-ALPHA開發板可以正常啟動Linux內核。如果將imx6ull-alientek-emmc.dts根節點的compatible屬性改為其他的值,比如:
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ullll"
重新編譯DTS,並用新的DTS啟動Linux內核,結果如圖43.3.4.1所示的錯誤提示:
圖43.3.4.1 系統啟動信息
當我們修改了根節點compatible屬性內容以後,因為Linux內核找不到對應的設備,因此Linux內核無法啟動。在uboot輸出Startingkernel…以後就再也沒有其他信息輸出了。
接下來我們簡單看一下Linux內核是如何根據設備樹根節點的compatible屬性來匹配出對應的machine_desc,Linux內核調用start_kernel函數來啟動內核,start_kernel函數會調用setup_arch函數來匹配machine_desc,setup_arch函數定義在文件arch/arm/kernel/setup.c中,函數內容如下(有縮減):
示例代碼43.3.4.6 setup_arch函數內容
913void __init setup_arch(char**cmdline_p)
914{
915conststruct machine_desc *mdesc;
916
917 setup_processor();
918 mdesc = setup_machine_fdt(__atags_pointer);
919if(!mdesc)
920 mdesc = setup_machine_tags
(__atags_pointer, __machine_arch_type);921 machine_desc = mdesc;
922 machine_name = mdesc->name;
......
986}
第918行,調用setup_machine_fdt函數來獲取匹配的machine_desc,參數就是atags的首地址,也就是uboot傳遞給Linux內核的dtb文件首地址,setup_machine_fdt函數的返回值就是找到的最匹配的machine_desc。
函數setup_machine_fdt定義在文件arch/arm/kernel/devtree.c中,內容如下(有縮減):
示例代碼43.3.4.7 setup_machine_fdt函數內容
204conststruct machine_desc * __init setup_machine_fdt(unsignedint dt_phys)
205{
206conststruct machine_desc *mdesc,*mdesc_best =NULL;
......
214
215if(!dt_phys ||!early_init_dt_verify(phys_to_virt(dt_phys)))
216returnNULL;
217
218 mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
219
......
247 __machine_arch_type = mdesc-> nr;
248
249return mdesc;
250}
第218行,調用函數of_flat_dt_match_machine來獲取匹配的machine_desc,參數mdesc_best是默認的machine_desc,參數arch_get_next_mach是個函數,此函數定義在定義在arch/arm/kernel/devtree.c文件中。找到匹配的machine_desc的過程就是用設備樹根節點的compatible屬性值和Linux內核中保存的所以machine_desc結構的. dt_compat中的值比較,看看那個相等,如果相等的話就表示找到匹配的machine_desc,arch_get_next_mach函數的工作就是獲取Linux內核中下一個machine_desc結構體。
最後在來看一下of_flat_dt_match_machine函數,此函數定義在文件drivers/of/fdt.c中,內容如下(有縮減):
示例代碼43.3.4.8 of_flat_dt_match_machine函數內容
705constvoid* __init of_flat_dt_match_machine(constvoid*default_match,
706constvoid*(*get_next_compat
)(constchar*const**))707{
708constvoid*data =NULL;
709constvoid*best_data = default_match;
710constchar*const*compat;
711unsignedlong dt_root;
712unsignedint best_score =~1, score =0;
713
714 dt_root = of_get_flat_dt_root();
715while((data = get_next_compat(&compat))){
716 score = of_flat_dt_match(dt_root, compat);
717if(score >0&& score < best_score){
718 best_data = data;
719 best_score = score;
720}
721}
......
739
740 pr_info("Machine model: %s\\n", of_flat_dt_get_machine_name());
741
742return best_data
;743}
第714行,通過函數of_get_flat_dt_root獲取設備樹根節點。
第715~720行,此循環就是查找匹配的machine_desc過程,第716行的of_flat_dt_match函數會將根節點compatible屬性的值和每個machine_desc結構體中. dt_compat的值進行比較,直至找到匹配的那個machine_desc。
總結一下,Linux內核通過根節點compatible屬性找到對應的設備的函數調用過程,如圖43.3.4.2所示:
圖43.3.4.2 查找匹配設備的過程
閱讀更多 正點原子 的文章