「正點原子Linux連載」第四十三章Linux設備樹(一)

1)實驗平臺:正點原子Linux開發板

2)

摘自《正點原子I.MX6U嵌入式Linux驅動開發指南

「正點原子Linux連載」第四十三章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所示:


「正點原子Linux連載」第四十三章Linux設備樹(一)


圖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所示:


「正點原子Linux連載」第四十三章Linux設備樹(一)


表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所示的錯誤提示:


「正點原子Linux連載」第四十三章Linux設備樹(一)


圖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所示:


「正點原子Linux連載」第四十三章Linux設備樹(一)


圖43.3.4.2 查找匹配設備的過程


分享到:


相關文章: