小白帶你探索Linux設備樹1_框架篇V1
0. 說在前面的話
學習過程中的總結記錄,感悟比較淺顯,經不起推敲,歡迎批評指正!!!
文章主體是以imx8qm-mek評估板爲實例進行介紹設備樹。
1. 什麼是設備樹
1.1 設備樹的定義
官方定義:
The primary purpose of Device Tree in Linux is to provide a way to
describe non-discoverable hardware. This information was previously
hard coded in source code.
ARM設備樹出現之前的電路板硬件的細節被硬編碼到內核中了,導致內核代碼臃腫難以維護,因此將內核與那些臃腫的硬件代碼解耦,方便維護。
1.2 設備樹的使用
首先看下設備樹如何被被加載到內核當中的過程
首先將上圖涉及到的dts、dtb、dtc簡單說明下。
- dts:device tree source 設備樹源文件,也就是我們進行編輯的文件,用於增刪相關的硬件細節,可以看做C之類的代碼,並且支持部分C語法,後面會詳談;
- dtb:device tree blob 可以視爲二進制版本的dts,內核可以解析;
- dtc:device tree compiler 設備樹編譯器,dtb文件就是通過dtc將dts文件編譯而成的。個人感覺可以類比C語言的編譯。
上圖可以總結爲:利用dtc將dts文件編譯爲可被內核解析的dtb文件,通過bootloader的引導,kernel會將dtb文件加載到內存,並進行解析。
在沒引進設備樹之前,當每次更改設備,都需要重新編譯內核,而現在僅僅需要編譯設備樹即可,省時省力。
2. ARM Linux設備樹的起源
2.1 忍無可忍
設備樹出現的原因,還得從2011年3月17日Linus Torvalds的那封信說起,下面是信件的截圖:
因ARM帶來大量的板級細節代碼,導致內核中充斥着大量的垃圾代碼,最後Linus忍無可忍了,於是乎這就成了ARM Linux設備樹誕生的契機。
2.2 事件背景
每次正式的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撞在槍口上了),他發出了怒吼:
對事件背景感興趣的詳見wowo寫的Device Tree(一):背景介紹,我就不在這裏班門弄斧了。
3. DT的文件組織結構
3.1 dts目錄簡介
dts文件一般位於./arch/xxx/boot/dts目錄中,首先映入眼簾的是各大SOC廠商的目錄
我們這裏選擇freescale也就是現在的NXP作爲分析對象
3.2 dts分析文件下載
如果大家沒有內核源代碼的話,可以使用如下命令將我們的作案工具單獨下載下來,不用clone整個內核源代碼,耗時又費力!!!
git clone https://github.com/sazczmh/imx8qm_linux_dts.git
如果clone不了的話,也可以從我的百度雲盤下載
鏈接: https://pan.baidu.com/s/15lg0u54RMNjaMkge5e3E4g 密碼: nsnv
clone後的代碼如下
3.3 完整dts文件的組成
從上圖可以看到給大家的倉庫中,不僅有.dts後綴的文件還有.dtsi後綴的文件,還記得我們之前提到過的dts支持部分C語法嗎?這裏的.dtsi類似C語言的頭文件。
爲了模塊化dts文件,驅動工程師將電路板公用的部分提煉爲各個.dtsi文件,減少代碼的重複,並且易於移植。下圖就是頭文件包含示例:
從圖中也可以明顯的注意到,該文件還包含了.h文件,是不是有點C的趕腳。
下面我們簡單分析下倉庫裏那麼多dts、dtsi的組織,在分析之前需要明白以下幾個點:
- 倉庫裏設備樹文件不是一塊評估板的設備樹,是同一芯片imx8qm的多個評估板的設備樹;
- 同一塊評估板因爲用途不同,有多種設備樹進行個性化配置;
- dtsi文件是提煉出來的公用的模塊文件,爲的是減少代碼重複,易於移植。
明白了以上幾個點之後,讓我們着手分析吧
首先拋棄dtsi文件僅僅分析dts文件
從圖中可以明顯的看到imx8qm的評估板種類有三種(我猜的…,大概是的),咱們的重點放在mek-Multisensory Enablement Kit系列,關於這幾個文件的解釋可以查看官網的release note,相關截圖如下。
從上圖可以明顯的看出來,mek那麼多設備樹分別有不同的特性。下面我們就僅用fsl-imx8qm-mek.dts來進行示例分析。
如下是fsl-imx8qm-mek.dts的層次結構
從上圖也可以看出一個完整的設備樹文件是由1個dts文件+n個dtsi文件組成的。
4. DT的語法組織框架
4.1 DT的基本語法
/dts-v1/;
/ {
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
// hex is implied in byte arrays. no '0x' prefix is required
a-byte-data-property = [01 23 34 56];
child-node1 {
first-child-property;
second-child-property = <1>;
a-string-property = "Hello, world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
child-node1 {
};
};
};
該抽象框架引自https://elinux.org/Device_Tree_Usage
說實話,小白我第一次遇到這個語法圖的時候,真的是一臉懵逼,這個語法圖太抽象了,和真實的設備樹文件匹配不上,真實的設備樹文件如下:
如果這時候大家還沒有厭煩我的嘮叨的話,就且聽我慢慢道來dts語法組織框架吧。
- 唯一的根節點: “/” ,多個根節點會合並;
- 根節點會包含一系列子節點,抽象框架裏面有兩個子節點,它們分別是"node1" 和 “node2”;
- 子節點"node1“又包含一系列子節點,這個抽象框架裏有兩個子節點,它們分別是: “child-node1” and “child-node2”;
- 各個節點又包含一系列屬性。
屬性是簡單的關鍵詞和其對應的值所組成,與關鍵詞所對應的值可以爲空或者任意的數據屬性,下面簡單說說設備樹源文件幾個常見的屬性例子:
- 文本字符串數據屬性以雙引號的形式展示出來:
a-string-property = "A string";
- ‘Cells’ 屬性是32 bit 無符號整數,並且由尖括號來定位:
cell-property = <0xbeef 123 0xabcd1234>;
- 二進制數據屬性由方括號來定位:
binary-property = [0x01 0x23 0x45 0x67];
- 可以使用逗號將不同表示形式的數據連接在一起:
mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
- 逗號還可用於創建字符串列表:
string-list = "red fish", "blue fish";
說過基本的語法,接下來讓我們開始實戰。
4.2 DT的簡化框架
當了解基本的DT語法之後,首先展示一副簡化的DT框架,讓大家對DT有個簡單的認識
https://www.nxp.com/docs/en/application-note/AN5125.pdf
4.3 根節點的model與compatible
-
model屬性指明瞭該評估板的設備生產商(感覺主要指的芯片生產商)、該評估板(也可指芯片系列)的名字,一般而言,該屬性的數值爲“manufacturer,model”,本示例當中
model = "Freescale i.MX8QM MEK";
指明瞭該評估板的生產商爲Freescale,評估板的名字爲i.MX8QM MEK。 -
compatible屬性是上文所提到的字符串列表屬性,這些屬性用於匹配machine type的,可以據此判斷啓動的設備。
string-list = "red fish", "blue fish";
根節點的compatible屬性一般包括兩個以上的兼容性字符串,本例中爲
compatible = "fsl,imx8qm-mek", "fsl,imx8qm";
- fsl,imx8qm-mek爲板子級別的名字,代表imx8qm的MEK評估板;
- fsl,imx8qm爲芯片級別的名字,代表imx8qm這款SOC。
這裏面要提一點根節點僅有一個,但是觀察dtsi文件發現,存在多個根節點,dtc會將多個根節點進行合併,類似下圖。
https://bootlin.com/pub/conferences/2014/elc/petazzoni-device-tree-dummies/
4.4 節點名字
下面集中列舉幾個節點名字,先有個感性的認識。
sata: sata@5f020000
memory@80000000
reserved-memory
節點名字具有如下格式
[label: ]<name>[@<unit-address>]
- name是由簡單的ascii字符串,最長31字符。通常情況下name的命名是說明這個設備節點的種類,例如sata、memory。
- unit-address指的是該設備節點的地址,會在reg屬性中具體指定,節點名字中可以不包含,但通常情況下都加上個unit-address。
- label: 相當於該設備節點的指針,方便其他設備節點引用,或者形成層次性的結構。
- 方便其他設備引用
對於gic: interrupt-controller@51a00000
來說, 設備節點通過&gic
來引用該設備節點
- 形成層次性的結構
對於sata: sata@5f020000
,該設備節點在fsl-imx8qm-device.dtsi中進行定義了
然後又在fsl-imx8qm-mek.dtsi中進行進一步配置
- 方便其他設備引用
4.5 reg屬性
先來看個reg屬性實例
首先reg的第一個地址,對應着unit-address,reg格式的定義由父節點的如下屬性所控制。
#address-cells
#size-cells
reg屬性的語法如下
reg = <address1 length1 [address2 length2] [address3 length3] ... >
sata父節點的reg屬性的限制如下
另外再提一下,reg是根據該soc的RM中描述,下面舉個例子
最後爲了驅動中方便查詢,通常給利用reg-name屬性給reg起個別名,sata的reg-name如下
reg-names = "ctl", "phy";
4.6 interrupts屬性
sata的中斷屬性如下
interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>;
該屬性由interrupt-parent屬性所限定,如果該節點沒有指定interrupt-parent,那麼將有父節點的interrupt-parent所限定,sata的父節點的相應屬性爲。
interrupt-parent = <&gic>;
gic節點的定義爲
gic: interrupt-controller@51a00000 {
compatible = "arm,gic-v3";
reg = <0x0 0x51a00000 0 0x10000>, /* GIC Dist */
<0x0 0x51b00000 0 0xC0000>, /* GICR */
<0x0 0x52000000 0 0x2000>, /* GICC */
<0x0 0x52010000 0 0x1000>, /* GICH */
<0x0 0x52020000 0 0x20000>; /* GICV */
#interrupt-cells = <3>;
interrupt-controller;
interrupts = <GIC_PPI 9
(GIC_CPU_MASK_SIMPLE(6) | IRQ_TYPE_LEVEL_HIGH)>;
interrupt-parent = <&gic>;
};
注意如下幾個宏定義均在相應.h文件中進行聲明。
GIC_SPI
IRQ_TYPE_LEVEL_HIGH
4.7 其他屬性
clocks、power-domain、status…等屬性都是如法炮製,我就不在這裏多解釋了。
4.8 特殊節點
- aliases節點:某些節點的快捷訪問方式?官方是這樣解釋的,但我是沒看出來…。
aliases {
csi0 = &mipi_csi_0;
csi1 = &mipi_csi_1;
dpu0 = &dpu1;
dpu1 = &dpu2;
ethernet0 = &fec1;
ethernet1 = &fec2;
dsi_phy0 = &mipi_dsi_phy1;
dsi_phy1 = &mipi_dsi_phy2;
mipi_dsi0 = &mipi_dsi1;
mipi_dsi1 = &mipi_dsi2;
ldb0 = &ldb1;
ldb1 = &ldb2;
isi0 = &isi_0;
isi1 = &isi_1;
isi2 = &isi_2;
isi3 = &isi_3;
isi4 = &isi_4;
isi5 = &isi_5;
isi6 = &isi_6;
isi7 = &isi_7;
serial0 = &lpuart0;
serial1 = &lpuart1;
serial2 = &lpuart2;
serial3 = &lpuart3;
serial4 = &lpuart4;
mmc0 = &usdhc1;
mmc1 = &usdhc2;
mmc2 = &usdhc3;
usbphy0 = &usbphy1;
can0 = &flexcan1;
can1 = &flexcan2;
can2 = &flexcan3;
i2c0 = &i2c_rpbus_0;
i2c1 = &i2c_rpbus_1;
};
- chosen 節點:該節點不代表一個真實設備,通常用於固件和操作系統傳遞參數的橋樑,就像boot參數。而且其parent node必須是名字是“/”的根節點。
chosen {
bootargs = "console=ttyLP0,115200 earlycon=lpuart32,0x5a060000,115200";
stdout-path = &lpuart0;
};
5. 總結
本文以imx8qm-mek評估板爲實例進行介紹DT,讓大家對DT的定義、ARM Linux DT的起源、具體評估板DT的文件組織結構、DT的語法組織框架有個簡單的認識。本篇博客並沒有事無鉅細的介紹DT,僅僅是點到爲止,而且小編也是剛剛接觸設備樹,沒有太多的經驗,如發現錯誤,還望指出來,大家一起交流共同進步。
過段時間會推出本篇博客的續作小白帶你探索Linux設備樹2_API篇V1,進行總結一個driver如何使用設備樹。
參考文獻
主要是國外官方的權威資料
- https://elinux.org/Device_Tree_Reference
- https://elinux.org/Device_Tree_Usage
- https://bootlin.com/pub/conferences/2014/elc/petazzoni-device-tree-dummies/
- https://www.nxp.com/docs/en/application-note/AN5125.pdf
- http://www.wowotech.net/device_model/dt_basic_concept.html
原創不易,切勿剽竊!
歡迎大家關注我創建的微信公衆號——小白倉庫
原創經驗資料分享:包含但不僅限於FPGA、ARM、RISC-V、Linux、LabVIEW等軟硬件開發,另外分享生活中的趣事以及感悟。目的是建立一個平臺記錄學習過的知識,並分享出來自認爲有用的與感興趣的道友相互交流進步。