摘要:是不是所有的Linux內核都是完美的?畢竟諸多黑客效力于此,當然不是,至少在內核3.x版本之前不是,之前的代碼臃腫,代碼利用率較低,直到設備樹的引入,徹底改善這一情況;
一、FDT的概念
系統啟動時,Bootloader開始加載,將內核文件,如zImage讀取到內存中,內核按照我們的代碼,逐一去配置每個寄存器,每個外設,似乎沒有什么問題。但是試想一下,100種ARM芯片,就要寫100個配置文件么?當然,如果你非要這么做,我也無話可說。如果能抽象出一種數據結構,它可以直接抽象出內核需要配置的所有硬件以及硬件屬性,BootLoader預讀取到內存中,在內核啟動以后,可以直接配置,對于用戶而言,配置MCU的外圍時我們直接面對的就只是這個DTS文件,極其方便快捷。FDT準確來講是一種數據結構,使得硬件可以用形如XML的描述語言來描述。
二、設備樹結構
圖一 設備樹結構
設備樹一般包含以上內容:
根節點“/”下的model ,這個一般為字符串類型,它描述了廠商以及板子名稱;
根節點“/”下的compitable,這個一般為字符串類型,用以匹配model選定的開發板對應的代碼;包括后續外圍驅動的匹配均是有這個compitable來完成;
根節點“/”下的aliases,這個設備節點只能放在根節點目錄,主要用以存放外設的別名,簡單講,"/soc/aips-bus@02000000/spba-bus@02000000/serial@02020000"其實是一個串口,但是開發人員自己看起來并不直觀,我可以在aliases中寫作:serial ="/soc/aips-bus@02000000/spba-bus@02000000/serial@02020000";serial即可代替剛才的串口設備;
根節點“/”下的chosen:這個并非物理設備節點,而是內核啟動參數的節點,類似于uboot階段的bootargs參數;
當然,這個節點也可以是子節點,不一定要在根節點下;
實例:chosen {
stdout-path = &uart1;
};
?snvs@020b0000:除以上節點,剩下的我一般稱之為物理設備節點(可能不準確),以snvs外設舉例,直接舉例;
實例:snvs@020b0000{
conpitable = “fsl,imx6ul-snvs”;
reg = <0x020b0000 0x4000>;
interrupts = <0x0 0x4 0x4>;
};
(1)“@”后面緊跟就是該外設在MCU總線的地址,這個不難理解,可以理解為外設的基地址,外設模型 name@addresss;”
(2)“compitable”:如上陳述,非常關鍵的屬性,匹配外設驅動,屬性模型 compitable = “[manufacture,[model]]”;
(3)“reg”:該屬性為外設地址屬性,第一個參數為該節點總線地址,后者為地址長度;
(4)“interrupt”:顧名思義,該外設的中斷,para1表示該中斷是不是SPI中斷(shared peripheral interrupt),注意名詞區分,參數值為1表示為SPI中斷,反之不是SPI中斷;para2是該中斷號;para3表示觸發方式,參數值為1,表示上升沿觸發,為4表示高電平觸發;如果需要低電平以及下降沿觸發,硬件需要加非門;
三、編譯設備樹與反編譯
設備樹編譯,我們都知道使用如下命令編譯:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs 或者
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all
實際上,是dtc這個文件在負責把dts解釋成dtb文件,該文件在內核源碼根目錄 ./scripts/dtc
編譯命令:
./scripts/dtc/dtc –I dts –O dtb /home/gyh/tmp/imx6y2c-256m.dtb ./arch/arm/boot/dts/imx6y2c-256m.dts
反編譯命令:
./scripts/dtc/dtc -I dtb -O dts -o /home/gyh/tmp/imx6y2c_asm.dts ./arch/arm/boot/dts/imx6y2c-256m.dtb
對于Linux命令的使用,可以使用help cmdname 或者man cmdname,對于dtc,非內建命令,man dtc:
-I <input format>
Input formats are:
dts - device tree source text
dtb - device tree blob
fs - /proc/device-tree style directory
-O <output format>
Output formats are:
dts - device tree source text
dtb - device tree blob
asm - assembler source
系統提供的dts一般引用dtsi這個母設備樹,所以大量外設都是直接引用dtsi中的,因此很難理解這些字符串是怎樣的匹配驅動程序的,但是一旦將已經生產的dtb文件反編譯,生產的dts文件將更直觀;但是易讀性也更差。這并不矛盾;我選擇,” /” ,”chosen” ,”aliases”三個節點來對比。
圖二 BSP提供的dts文件
圖三 反編譯的dts文件
對同一個chosen節點:BSP中dts描述為stdout-path = &uart1;這樣很難想象它是怎樣把該外設定義為標準輸出的,但是如果看反編譯文件可以較好的理解,標準輸出被重定向到某個可以作為輸出的外設地址;
四、設備樹節點添加與驗證
(1)直接在dts文件中查找,是否已經存在你需要的外設節點;如果有,且該外設支持多從機或者多節點,直接在該節點下面,添加子節點,以GPIO_LED為例。
圖四 GPIO_LEDS節點
(2)假設,你需要添加一個黃色的LED,那么仿照已經存在的節點,復制一個節點在母節點下,命名為green-led,同時用GPIO3_4為該LED驅動引腳;你希望在arm板上叫他,My_Cute(這個名字不好),那么最后修改如下:
圖五 增加yellow-led節點
(3)節點添加完成,引用了GPIO3_4,所以你需要確認該MCU引腳已經配置為GPIO功能,這里直接貼出配置代碼:MX6UL_PAD_LCD_RESET__GPIO3_IO04 0x40017059
圖六 引腳配置為GPIO
該宏定義MX6UL_PAD_LCD_RESET__GPIO3_IO04在./arch/arm/boot/dts/imx6ull-pinfunc.h中;針對同一個引腳的全部復用,均定義了宏,可以直接調用;該dts并未直接包含imx6ull-pinfunc.h,在其他dtsi中已經包含該頭文件;
(4)如果之前已經完全編譯過內核,可以直接編譯dtb,注意不要make menuconfig或者defconfig,否則會覆蓋zImage的配置文件.config;
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
(5)編譯完成后,開發板直接進入uboot模式,tftp網絡燒寫dtb,reset重啟生效;
run updtb
(備注:updtb為組合命令updtb=if tftp ${fdt_file}; then nand erase.part dtb; nand write ${loadaddr} dtb ${filesize}; fi;)
(6)如果dtb按照我們理解修改是正確的,那么我們將在開發板的/sys/class/leds下面看到我們的My_Cute這個LED節點;結果如下:
圖七 開發板設備截圖
其實,可以看到/sys/class/leds下面的設備節點都是指向/devices/platfome/leds目錄的連接文件,也就是這里僅僅是這個設備的“快捷方式”,我們也可以進行文件IO操作;
(7)文件IO操作:打開My_Cute節點,可以看到以下接口可以操作,但是我們在添加GPIO_LEDS并沒有添加這些屬性。Brightness, trigger—led亮度以及觸發方式比較常用,那么問題來了,為什么會有這些接口。因為它們繼承了母節點的屬性,所以我們需要找到母節點設備的定義。
圖八 yellow-led的操作接口
(8)講道理,所有的內核驅動你都可以嘗試在 ./driver/下面去找,針對led類,我們直接進入leds文件夾,發現leds的驅動leds-gpio.c在,在這里就可以理解led的接口為什么是這樣;當然優秀的驅動應該還有一份清晰的文檔,你同樣可也嘗試去源碼根目錄的. /Documentation 中查找leds-gpio的使用文檔;這里也會解釋,我為什么會去開發板的/sys/class/leds下面去查看我增加的My_Cute節點;
圖九 驅動使用文檔
(9)增加一個驅動或者一個設備節點到設備樹中,你可以先查看內核源碼的/ Documentation目錄,其中包含了幾乎所有驅動的使用說明以及設備樹屬性的解釋,同時也包括大量優秀的內核調試技巧;再去寫節點,也可以先模仿,針對不懂的地方再來看文檔,印象更為深刻。
五、結語
設備樹相比于傳統的配置文件,無疑是降低了Linux外設開發與使用的門檻,但是也隱藏了大量的細節,難以了解其底層的驅動原理;對于LINUX內核的了解,我所認識的還不及冰山一角,單希望對你有一點幫助。