????設備樹(DTS:device tree source),字面意思就是一塊電路板上設備如上圖中CPU、DDR、I2C、GPIO、SPI等,按照樹形結(jié)構(gòu)描繪成的一棵樹。按照策略和功能分離的思路,就是驅(qū)動代碼(功能)和設備樹DTS配置文件(策略)分開來進行設計,這樣針對不同的電路板,Linux驅(qū)動代碼就不用動了,只需要改改DTS就可以,DTS中的配置會決定哪些驅(qū)動去運行。 ????Linux相關知識在嵌入式領域中很重要,要學習可以找一個能運行Linux代碼的環(huán)境,最好有一個開發(fā)板,也可以用qemu在ubuntu上運行,可以參考之前的文章:Linux驅(qū)動-IMX6ULL開發(fā)板qemu環(huán)境搭建或者自己搭建一個參考: https://zhuanlan.zhihu.com/p/521196386 ?
?設備樹起源
在Linux 2.6中,ARM架構(gòu)的板極硬件細節(jié)過多地被硬編碼在arch/arm/plat-xxx和arch/arm/mach-xxx中,如果外設發(fā)生相應的變化,那么驅(qū)動代碼就需要改動。 ????2011年,Linux之父Linus Torvalds發(fā)現(xiàn)這個問題后,就通過郵件向ARM-Linux開發(fā)社區(qū)發(fā)了一封郵件,不禁的發(fā)出了一句“This wholeARM thing is a f*cking pain in the ass”。之后,ARM社區(qū)就引入了PowerPC等架構(gòu)已經(jīng)采用的設備樹(Flattened Device Tree)機制,將板級信息內(nèi)容都從Linux內(nèi)核中分離開來,用一個專屬的文件格式來描述,即現(xiàn)在的.dts文件。 ????
從 3.x 版本之后開始支持使用設備樹,這樣做的意義重大,可以實現(xiàn)驅(qū)動代碼與設備的硬件信息相互的隔離,減少了代碼中的耦合性。通過設備樹對硬件信息的抽象,驅(qū)動代碼只要負責處理邏輯,而關于設備的具體信息存放到設備樹文件中,這樣,如果只是硬件接口信息的變化而沒有驅(qū)動邏輯的變化,開發(fā)者只需要修改設備樹文件信息,不需要改寫驅(qū)動代碼。 ????
設備樹由一系列被命名的節(jié)點(Node)和屬性(Property)組成,而節(jié)點本身可包含子節(jié)點。在設備樹中,可描述的信息包括:
CPU的數(shù)量和類別。
內(nèi)存基地址和大小。
總線和橋。
外設連接。
中斷控制器和中斷使用情況。
GPIO控制器和GPIO使用情況。
時鐘控制器和時鐘使用情況。
基本上就是畫一棵電路板上CPU、總線、設備組成的樹,Bootloader會將這棵樹傳遞給內(nèi)核,然后內(nèi)核可以識別這棵樹,并根據(jù)它展開出Linux內(nèi)核中的platform_device、i2c_client、spi_device等設備,而這些設備用到的內(nèi)存、IRQ等資源,也被傳遞給了內(nèi)核,內(nèi)核會將這些資源綁定給展開的相應的設備。 ?
2.?基本概念介紹 2.1 dts
dts(device tree source設備樹源文件)文件是一種ASCII文本格式的設備樹描述文件,此文件適合人類閱讀,主要是給用戶看的。
硬件的相應信息都會寫在.dts為后綴的文件中,每一款硬件可以單獨寫一份xxxx.dts,一般在Linux源碼中存在大量的dts文件,對于 arm 架構(gòu)可以在arch/arm/boot/dts找到相應的dts,另外mips則在arch/mips/boot/dts,powerpc在arch/powerpc/boot/dts。
對于imx6ull開發(fā)板 arch/arm/boot/dts/100ask_imx6ull_qemu.dts
dts中一般會包一個公共部分的dtsi文件,如下:
#include"imx6ull.dtsi" ?
2.2 dtsi
值得一提的是,對于一些相同的dts配置可以抽象到dtsi文件中,然后類似于 C 語言的方式可以include到dts文件中,對于imx6ull開發(fā)板arch/arm/boot/dts/imx6ull.dtsi
對于同一個節(jié)點的設置情況,dts中的配置會覆蓋dtsi中的配置。具體如下圖所示;
2.3 dtc
dtc是編譯dts的工具,可以在Ubuntu系統(tǒng)上通過指令apt-get install device-tree-compiler安裝dtc工具,不過在內(nèi)核源碼scripts/dtc路徑下已經(jīng)包含了dtc工具;
2.4 dtb
dtb(DeviceTree Blob),dts經(jīng)過dtc編譯之后會得到dtb文件,dtb通過Bootloader引導程序加載到內(nèi)核。所以Bootloader需要支持設備樹才行;Kernel 也需要加入設備樹的支持;
dtb文件布局如下:
從上圖可以看出,DTB文件主要包含四部分內(nèi)容:
struct ftdheader:用來表明各個分部的偏移地址,整個文件的大小,版本號等;
memory reservation block:在設備樹中使用/memreserve/ 定義的保留內(nèi)存信息;
structure block:保存節(jié)點的信息,節(jié)點的結(jié)構(gòu);
strings block:保存屬性的名字,單獨作為字符串保存;
dtb文件代碼級別的解析可以參考: https://cloud.tencent.com/developer/article/1887823
(1) dtb 文件的結(jié)構(gòu)圖如下:
(2) 設備節(jié)點的結(jié)構(gòu)圖如下:
2.5 DTB加載及解析過程
U-Boot處理如下:
3. DTS基本框架
下圖是一個設備樹文件的基本架構(gòu);大概看了一下有點類似于XML文件,簡單概括一下有這幾個部分;
一個例子:
1 個雙核ARM Cortex-A932 位處理器;ARM 本地總線上的內(nèi)存映射區(qū)域分布有
兩個串口(分別位于0x101F1000和0x101F2000)
GPIO控制器(位于0x101F3000)
SPI控制器(位于0x10170000)
中斷控制器(位于0x10140000)
外部總線橋上連接的設備如下:
SMC SMC91111以太網(wǎng)(位于0x10100000)
I2C控制器(位于0x10160000)
64MB NOR Flash(位于0x30000000) 外部總線橋上連接的 I2C 控制器所對應的 I2C 總線上又連接了MaximDS1338實時鐘(I2C 地址為0x58)具體如下圖所示; 一個移植網(wǎng)卡的例子:
比如dm9000網(wǎng)卡,就需要首先將示例信息掛接到我們的板級設備樹上,并根據(jù)芯片手冊和電路原理圖將相應的屬性進行配置,再配置相應的驅(qū)動。需要注意的是,dm9000的地址線一般是接在片選線上的,所以設備樹中就應該歸屬與相應片選線節(jié)點,我這里用的exynos4412,接在了bank1,所以是"<0x500000000x2 0x50000004 0x2>"
最終的配置結(jié)果是:
然后make menuconfig勾選相應的選項將dm9000的驅(qū)動編譯進內(nèi)核。
?
?
[*] Networking support ---> Networking options ---> <*> Packet socket <*>Unix domain sockets [*] TCP/IP networking [*] IP: kernel level autoconfigurationDevice Drivers ---> [*] Network device support ---> [*] Ethernet driver support (NEW) ---> <*> DM9000 supportFile systems ---> [*] Network File Systems (NEW) ---> <*> NFS client support [*] NFS client support for NFS version 3 [*] NFS client support for the NFSv3 ACL protocol extension [*] Root file system on NFS執(zhí)行make uImage;make dtbs,tftp下載,成功加載nfs根文件系統(tǒng)并進入系統(tǒng),表示網(wǎng)卡移植成功
?
?
詳細語法參考:https://www.cnblogs.com/xiaojiang1025/p/6131381.html ? 4. 修改DTS試驗 4.1 dts修改 ????修改設備樹文件 arch/arm/boot/dts/100ask_imx6ull_qemu.dts,添加一個我們自己的模塊dts_tree1:
修改完成后執(zhí)行make dtbs 重新編譯設備樹文件,編譯完成后arch/arm/boot/dts/100ask_imx6ull_qemu.dtb,將其下載到芯片中。
或者用qemu運行的時候,修改參考指向這個新的dtb文件。
查看設備樹節(jié)點進入內(nèi)核,執(zhí)行
ls ?/proc/device-tree/ |
我們會發(fā)現(xiàn)剛剛創(chuàng)建的設備樹節(jié)已經(jīng)存在了
跟我們在dts里面修改的一樣,這里變成了一個個的文件形式。文件的名字是屬性的名字,內(nèi)容是值。
具體看看節(jié)點的內(nèi)容,執(zhí)行
? 4.2 內(nèi)核中添加驅(qū)動模塊
參考:Linux驅(qū)動實踐:帶你一步一步編譯內(nèi)核驅(qū)動程序 - IOT物聯(lián)網(wǎng)小鎮(zhèn) - 博客園
在/drivers文件夾下創(chuàng)建dts_test文件夾,然后創(chuàng)建Kconfig文件
?
Bash ?config ?DTS_TEST ???????? tristate "dts test" ???????? default y ???????? help ???????????? This is the dts test |
?
創(chuàng)建Makefile文件
?
JavaScript ?obj-$(CONFIG_DTS_TEST) += dts_test.o |
?
在drivers文件夾下的Kconfig和Makefile文件中分別添加
?
C ?source ?"drivers/dts_test/Kconfig" ?obj-$(CONFIG_DTS_TEST)??????????????? += dts_test/ |
?
創(chuàng)建dts_test.c文件
?
C++ ?#include ? ?#include ?#include ?#include ?#include ? ?#include ?#include ?#include ?#include ?#include ?#include ?#include ?#include ?#include ?#include ?#include ?#include ?#include ?#include ? ?#define DRIVER_NAME "imx6ul,dts-tree" ? ? ?static int devtree_probe(struct platform_device * pdev) ?{ ???? struct fwnode_handle *child; ???? const char *p1,*p2[3]; ???? u32 p3[2],value; ???? u8 testmac[6]; ???? int i=0; ? ???? printk(KERN_INFO ?" **********devtree_probe****************** "); ? ???? ?device_property_read_string(&pdev->dev,"test-string",&p1); ???? printk("devtree_probe ?node? test-string is: %s ",p1); ? ???? ?device_property_read_string_array(&pdev->dev, ?"test-strings", p2, 3); ???? printk("devtree_probe node ?test-strings is: %s%s%s ",p2[0],p2[1],p2[2]); ? ???? ?device_property_read_u32(&pdev->dev,"test-u32",&value); ???? printk("devtree_probe node ?test-u32 is: <%d> ",value); ? ???? ?device_property_read_u32_array(&pdev->dev, ?"test-u32s", p3, 2); ???? printk("devtree_probe node ?test-u32s is: <%d>,<%d> ",p3[0],p3[1]); ? ???? ?device_property_read_string(&pdev->dev,"compatible",&p1); ???? printk("devtree_probe node ?compatible is: %s ",p1); ? ???? ?device_property_read_string(&pdev->dev,"status",&p1); ???? printk("devtree_probe ?node? status is: %s ",p1); ? ???? printk(" * devtree_probe ?child node "); ? ???? ?device_for_each_child_node(&pdev->dev, child){ ? ???????? printk("*************childnode%d************* ",i++);????? ???????? ?fwnode_property_read_string(child,"test-string",&p1); ???????? printk("childnode ?test-string is: %s ",p1); ? ???????? ?fwnode_property_read_string_array(child,"test-strings",p2,3); ???????? printk("childnode? test-strings is: ?%s%s%s ",p2[0],p2[1],p2[2]); ? ???????? ?fwnode_property_read_u32_array(child,"test-u32",&value,1); ???????? printk("childnode test-u32 ?is: <%d> ",value); ? ???????? ?fwnode_property_read_u32_array(child,"test-u32s",p3,2); ???????? printk("childnode? test-u32s is: ?<%d>,<%d> ",p3[0],p3[1]); ? ???????? ?fwnode_property_read_u8_array(child,"test-u8s",testmac,6); ???????? printk("childnode ?test-u32s is: ?[%x,%x,%x,%x,%x,%x] ",testmac[0],testmac[1],testmac[2],testmac[3],testmac[4],testmac[5]);? ? ???? } ? ???? return 0; ?} ?static int devtree_remove(struct platform_device * pdev) ?{ ???? printk(KERN_INFO ?"devtree_remove "); ? ???? return 0; ?} ? ?static const struct of_device_id of_devtree_dt_match[] = { ???? {.compatible = DRIVER_NAME}, ???? {}, ?}; ? ?MODULE_DEVICE_TABLE(of,of_devtree_dt_match); ? ?static struct platform_driver devtree_test_driver = { ???? .probe? = devtree_probe, ???? .remove = devtree_remove, ???? .driver = { ???????? .name = DRIVER_NAME, ???????? .owner = THIS_MODULE, ???????? .of_match_table = of_devtree_dt_match, ???????? }, ?}; ? ? ?static int devtree_test_init(void) ?{ ???? int num=0,i=0,value; ???? const char *p1; ???? struct device_node ?*node1,*childnode1; ???? u32 p2[2]; ???? u8 testmac[6]; ???? ???? pr_warn(KERN_INFO ?"^^^^^^^^^^^^^^^^^^^devtree_test_init^^^^^^^^^^^ "); ???? printk(KERN_INFO ?"^^^^^^^^^^^^^^^^^^^devtree_test_init^^^^^^^^^^^ "); ???? printk(" *************devtree ?init start *************** "); ? ???? node1 = ?of_find_node_by_path("/dts-tree1"); ???? if(node1 == NULL){ ???????? printk("of_find_node_by_path ?failed "); ???????? return -ENODEV; ???? } ???? else{ ???????? ?printk("of_find_node_by_path dts-tree1 ok "); ???? } ???? //read string ???? of_property_read_string(node1, ?"test-string", &p1); ???? printk("dts-tree1 node ?:test-string is: %s ",p1); ???? //read strings ???? num = ?of_property_count_strings(node1, "test-strings"); ???? printk("dts-tree1 node? test-strings num is: %d ",num); ???? for(i=0;i ???????? printk("%s",p1); ???? } ???? //read string ?"compatible" ???? of_property_read_string(node1, ?"compatible", &p1); ???? printk("dts-tree1 node? compatible is: %s ",p1); ? ???? //read string "status" ???? of_property_read_string(node1, ?"status", &p1); ???? printk("dts-tree1 node ??status is: %s ",p1); ? ???? //read u32 "test-u32" ???? ?of_property_read_u32(node1,"test-u32",&value); ???? printk("dts-tree1 node? test-u32 is: <%d> ",value); ? ???? //read? u32s test-u32s ???? of_property_read_u32_array(node1, ?"test-u32s", p2, 2); ???? printk("dts-tree1 node? test-u32s is: ?<%d>,<%d> ",p2[0],p2[1]); ? ???? //read u8s test-u8s ???? of_property_read_u8_array(node1, ?"test-u8s", testmac, 6); ???? printk("dts-tree1 node ?test-u8s is: ?<%x>,<%x>,<%x>,<%x>,<%x>,<%x> ",testmac[0],testmac[1],testmac[2],testmac[3],testmac[4],testmac[5]); ? ???? //get "dts_child_node1" ?device node? ???? childnode1 = ?of_get_child_by_name(node1,"dts_child_node1"); ???? if(childnode1 == NULL){ ???????? ?printk("of_get_child_by_name failed "); ???????? return -ENODEV; ???? } ???? printk("of_get_child_by_name ?dts_child_node1 ok "); ???? of_property_read_string(childnode1, ?"test-string", &p1); ???? printk("dts_child_node1 node ?test-string is: %s ",p1); ? ? ???? return ?platform_driver_register(&devtree_test_driver); ?} ? ?static void devtree_test_exit(void) ?{ ???? printk(KERN_INFO ?" devtree_test_exit "); ???? ?platform_driver_unregister(&devtree_test_driver); ?} ? ?module_init(devtree_test_init); ?module_exit(devtree_test_exit); ? ?MODULE_LICENSE("GPL"); ?MODULE_AUTHOR("zheng"); |
?
Kconfig中是y,這樣系編譯運行后,會直接看到打?。?/p>
? ? 4.3 常用OF API
linux 內(nèi)核中和設備樹相關的函數(shù)內(nèi)核關于設備樹的驅(qū)動都放在/drivers/of下,用戶可以使用這里面的函數(shù)對設備樹進行操作。
? 編輯:黃飛
?
評論
查看更多