什麼是DTS?為什麼要引入DTS?
DTS即Device Tree Source 裝置樹原始碼, Device Tree是一種描述硬體的資料結構,它起源於 OpenFirmware (OF)。在Linux 2.6中,ARM架構的板極硬體細節過多地被硬編碼在arch/arm/plat-xxx和arch/arm/mach-xxx,
比如板上的platform裝置、resource、i2c_board_info、spi_board_info以及各種硬體的platform_data,這些板級細節程式碼對核心來講只不過是垃圾程式碼。
而採用Device Tree後,許多硬體的細節可以直接透過它傳遞給Linux,而不再需要在kernel中進行大量的冗餘編碼。每次正式的linux kernel release之後都會有兩週的merge window,在這個視窗期間,kernel各個部分的維護者都會提交各自的patch,將自己測試穩定的程式碼請求併入kernel main line。
每到這個時候,Linus就會比較繁忙,他需要從各個核心維護者的分支上取得最新程式碼並merge到自己的kernel source tree中。Tony Lindgren,核心OMAP development tree的維護者,傳送了一個郵件給Linus,請求提交OMAP平臺程式碼修改,並給出了一些細節描述:1)簡單介紹本次改動2)關於如何解決merge conficts。有些git mergetool就可以處理,不能處理的,給出了詳細介紹和解決方案一切都很平常,也給出了足夠的資訊,然而,正是這個pull request引發了一場針對ARM linux的核心程式碼的爭論。
我相信Linus一定是對ARM相關的程式碼早就不爽了,ARM的merge工作量較大倒在其次,主要是他認為ARM很多的程式碼都是垃圾,程式碼裡面有若干愚蠢的table,而多個人在維護這個table,從而導致了衝突。因此,在處理完OMAP的pull request之後(Linus並非針對OMAP平臺,只是Tony Lindgren撞在槍口上了),他發出了怒吼:Gaah.Guys, this whole ARM thing is a f*cking pain in the ass.之後經過一些討論,對ARM平臺的相關code做出如下相關規範調整,這個也正是引入DTS的原因。1.ARM的核心程式碼仍然儲存在arch/arm目錄下2.ARM SoC core architecture code儲存在arch/arm目錄下3.ARM SOC的周邊外設模組的驅動儲存在drivers目錄下4.ARM SOC的特定程式碼在arch/arm/mach-xxx目錄下5.ARM SOC board specific的程式碼被移除,由DeviceTree機制來負責傳遞硬體拓撲和硬體資源資訊。本質上,Device Tree改變了原來用hardcode方式將HW 配置資訊嵌入到核心程式碼的方法,改用bootloader傳遞一個DB的形式。如果我們認為kernel是一個black box,那麼其輸入引數應該包括:a.識別platform的資訊
b.runtime的配置引數
c.裝置的拓撲結構以及特性對於嵌入式系統,在系統啟動階段,bootloader會載入核心並將控制權轉交給核心,此外,還需要把上述的三個引數資訊傳遞給kernel,以便kernel可以有較大的靈活性。
在linux kernel中,Device Tree的設計目標就是如此。
裝置樹檔案介紹在裝置樹出現之前,關於硬體的描述資訊一般放在一個個類似 arch/xxx/mach-xxx/board-xxx.c 的檔案中,如:
static struct resource gitchat_resource[] = { { .start = 0x20100000 , .end = 0x20100000 +1, .flags = IORESOURCE_MEM … .start = IRQ_PF IRQ_PF 15 , .end = IRQ_PF IRQ_PF 15 , .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE }};static struct platform_device gitchat_device = { .name name ="gitchat", .id = 0, .num_resources num_resources = ARRAY_SIZE(gitchat_resource), .resource = gitchat_resource,};static struct platform_device *ip0x_devices[] __initdata ={ &gitchat_device,};static int __init ip0x_init(void){ platform_add_devices(ip0x_devices, ARRAY_SIZE(ip0x_devices)); }
至於裝置樹的出現到底帶來了哪些好處,先看一下裝置樹的檔案:
eth:eth@ 4,c00000 { compatible ="csdn, gitchat"; reg =< 4 0x00c00000 0x2 4 0x00c00002 0x2 >; interrupt-parent =<&gpio 2>; interrupts=<14 IRQ_TYPE_LEVEL_LOW>; …};
從程式碼中可看到對於 GITCHAT 這個網絡卡驅動、一些暫存器、中斷號和上一層 gpio 節點都很清晰的被描述。比上一圖的程式碼優化了很多,也容易維護了很多。這樣就形成了裝置在指令碼,驅動在 c 檔案裡的關係圖:
從圖中可以看出 A、B、C 三個板子裡都含有 GITCHAT 裝置樹檔案,這樣對於 GITCHAT 驅動寫一份就可在 A、B、C 三個板子裡共用。從上幅圖裡不難看出,其實裝置樹的出現在軟體模型上相對於之前並沒有太大的改變,裝置樹的出現主要在裝置維護上有了更上一層樓的提高,此外在核心編譯上使核心更精簡,映象更小。
裝置樹的檔案結構和剖析裝置樹和裝置樹之間到底是什麼關係,有著哪些依賴和聯絡,先看下裝置樹之間的關係圖:
除了裝置樹(DTS)外,還存有 dtsi 檔案,就像程式碼裡的標頭檔案一樣,是不同裝置樹共有的裝置檔案,這不難理解,但是值得注意的是如果 dts 和 dtsi 裡都對某個屬性進行定義的話,底層覆蓋上層的屬性定義。這樣的好處是什麼呢?假如你要做一塊電路板,電路板裡有很多模組是已經存在的,這樣就可以直接像包含標頭檔案一樣把共性的 dtsi 檔案包含進來,大大減少工作量,後期也可以對類似模組再次利用。
裝置樹檔案的格式是 dts,包含的標頭檔案格式是 dtsi,dts 檔案是一種程式設計師可以看懂的格式,但是 Uboot 和 Linux 只能識別二進位制檔案,不能直接識別。所以就需要把 dts 檔案編譯成 dtb 檔案。把 dts 編譯成 dtb 檔案的工具是 dtc,位於核心目錄下 scripts/dtc,也可以手動安裝:sudo apt-get install device-tree-compiler 工具。具體 dts 是如何轉換成機器碼並在記憶體裡供 kernel 識別的,請看下圖:
裝置樹的應用有了理論,在具體的工程裡如何做裝置樹呢?這裡介紹三大法寶:文件、指令碼、程式碼。文件是對各種 node 的描述,位於核心 documentation/devicetree/bingdings/arm/ 下,指令碼就是裝置樹 dts,程式碼就是你要寫的裝置程式碼,一般位於 arch/arm/ 下,以後在寫裝置的時候可以用這種方法,絕對的事半功倍。很多上層應用開發者沒有做過核心開發的經驗,對核心一直覺得很神秘,其實可以換一種思路來看核心,相信上層應用開發者最熟悉的就是各種 API,工作中可以說就是和 API 打交道,對於核心也可以想象是各種 API,只不過是核心態的 API。這裡裝置檔案就是根據各種核心態的 API 來呼叫裝置樹裡的板級資訊。
struct device_node *of_find_node_by_phandle(phandle handle);
struct device_node *of_get_parent(const struct device_node_ *node);
of_get_child_count()
of_property_read_u32_array()
of_property_read_u64()
of_property_read_string()
of_property_read_string_array()
of_property_read_bool()
具體的用法這裡不做進一步的解釋,大家可以查詢資料或者看官網解釋。
這裡對裝置樹做個總結,裝置樹可以總結為三大作用:
一是平臺標識,所謂平臺標識就是板級識別,讓核心知道當前使用的是哪個開發板,這裡識別的方式是根據 root 節點下的 compatible 欄位來匹配。
二是執行時配置,就是在核心啟動的時候 ramdisk 的配置,比如 bootargs 的配置,ramdisk 的起始和結束地址。
三是裝置資訊集合,這也是最重要的資訊,集合了各種裝置控制器,接下來的實踐課會對這一作用重點應用。這裡有張圖對大家理解裝置樹作用有一定的幫助: