在linux中,dts是設備樹源文件,用于描述設備信息的;設備樹技術將設備的硬件資源信息就寫在dts文件中。設備樹源文件dts被編譯成dtb二進制,在bootloader運行時傳遞給操作系統,操作系統對其進行解析展開,從而產生一個硬件設備的拓撲圖,有了這個拓撲圖,在編成過程可以直接通過系統提供的接口獲取到設備樹的節點和屬性信息。
本教程操作環境:linux7.3系統、Dell G3電腦。
1、什么是設備樹?
設備樹(dt:device tree)是linux內核采用的參數表示和傳遞技術,在系統引導啟動階段進行設備初始化的時候,將設備樹中描述的硬件信息傳遞給操作系統;
-
dts(device tree source):設備樹源文件,描述設備信息的;
設備樹源文件dts被編譯成dtb二進制,在bootloader運行時傳遞給操作系統,操作系統對其進行解析展開,從而產生一個硬件設備的拓撲圖,有了這個拓撲圖,在編成過程可以直接通過系統提供的接口獲取到設備樹的節點和屬性信息
-
dtc(device tree compiler):設備樹編譯/反編譯/調試工具;
-
dtb(device tree binary):二進制設備樹鏡像;
-
dtsi(device tree source include):功能類似設備樹文件的頭文件,可以被dts文件通過include引用,dtsi文件一般是描述共性部分;
2、設備樹解決什么問題
-
在設備驅動源碼中,分為驅動代碼和設備代碼,驅動代碼是操作硬件的方法,設備代碼是硬件資源、數據,當驅動代碼和設備代碼匹配時就會調用驅動的probe函數,probe函數會利用設備代碼的資源去初始化設備;
-
設備樹之前,設備代碼都是直接寫在內核源碼中的,以platform_device結構體的形式存在,驅動代碼和設備代碼也是在platform總線上匹配,當需要修改設備資源時,就需要修改內核源碼;
-
設備樹技術將設備的硬件資源信息就寫在dts文件中,需要修改就修改dts文件,不必在修改內核源碼;
-
不采用設備樹技術:內核源碼中會充斥大量設備硬件描述信息,導致內核源碼不停增多,但是增多的硬件描述信息代碼和內核功能并不相關;
-
采用設備樹技術之后:設備的硬件描述信息都在dts文件中,修改方便,但是內核要增加解析dts文件格式的代碼;
3、設備樹怎么工作
-
驅動開發者根據硬件編寫/修改dts文件,使得將來驅動代碼能匹配到合適的設備硬件信息;
-
編譯內核時,kernel會先編譯出dtc,然后再用dtc將dts文件編譯成dtb;
-
uboot啟動kernel時,將內核鏡像和dtb都重定位到內存,并告訴內核dtb的所在內存地址;
-
內核啟動初期調用內部函數解析dtb,得到硬件信息后再組裝成硬件函數,最后去和驅動代碼進行匹配;
4、設備樹源碼dts文件格式講解
arm架構:arch/arm/boot/dts目錄中
-
注釋用/* */,注意#開頭的不是注釋
-
分號是段落塊之間的分隔符,{}和[]和是段落塊的封裝符號,和C語言語言類
-
/dts-v1/節點,表示dts的版本號,目前都是v1
-
/{}是根節點root node,理論上只應該有一個根節點,有說法dtc會合并所有root node為同一個
-
dts是樹狀的多節點組織,基本單元是node,除root外其他node都有parent,還可以有child
[label:] <node-name> [@<unit-address>]{ [property] [child nodes] [child nodes] ...... };
-
[]:表示該項可以省略,:表示不可省略;
[label:]:label是標簽名,為了方便訪問節點,后面可以直接通過&label來訪問該節點。
-
node-name:節點名稱。根節點的名稱必須是/
-
[@unit-address]:unit-address是設備地址,如cpu node就是0、1這種,reg node就是0x12010000這種;
cpus?{ /*?下面三項是cpus節點的屬性?*/ #address-cells?=?; #size-cells?=?; enable-method?=?"hisilicon,hi3516dv300"; /*?下面是子節點?*/ cpu@0?{ device_type?=?"cpu"; compatible?=?"arm,cortex-a7"; clock-frequency?=?<hi3516dv300_fixed_1000m>; reg?=?; }; };</hi3516dv300_fixed_1000m>
-
cpus是cpu的父節點,從形式來能直觀的看出來,cpu節點是被cpus節點的大括號括起來的;
-
cpus節點省略了標簽名和設備地址,只有節點名稱;
5、節點屬性分析
/{ gpx1:gpx1{ controller; #gpio-cells=; }; key@11400c24{ compatible="fs4412,key"; reg=; intn-key=; } }
-
gpio-controller:說明該節點描述的是一個gpio控制器;
-
#gpio-cells:描述gpio使用節點的屬性一個cell的內容;
uart0:?uart@120a0000?{ compatible?=?"arm,pl011",?"arm,primecell"; reg?=?; interrupts?=?; clocks?=?; clock-names?=?"apb_pclk"; status?=?"disabled"; }; /*?在驅動中對應的結構體*/ //struct?device_driver->of_match_table->compatible struct?of_device_id?{ char name[32]; char type[32]; char compatible[128]; const?void?*data; };
(1)compatible屬性是用于設備節點和設備驅動匹配用的,在內核描述驅動的structdevice_driver結構體中,compatible變量中就會保存用于匹配的字符串,當設備節點和驅動的
compatible相同時就匹配成功;
(2)compatible后面可以有多個字符串,優先匹配靠前的字符串,靠前的字符串匹配不上才會匹配后面的字符串;
/ { model = "Tyr DEMO Board"; compatible = "hisilicon,hi3516dv300"; memory { device_type = "memory"; reg = <0x82000000 0x20000000>; };};
(1)model是描述模塊信息的,一般只有根節點才有,標明設備樹文件對應的開發板的名稱;
(2)在內核的啟動打印中可以看到model的值:“OF: fdt:Machine model: Tyr DEMO Board”;
&uart0 { status = "okay"; };
狀態值 | 含義 |
---|---|
okey | 表示設備是可操作的 |
disabled | 表示當前不可操作,但是后續是可以更改為可操作性的 |
fail、failed | 表示有嚴重錯誤,幾乎不可能再可操作了 |
(1)status描述設備信息狀態,在設備樹文件中可以根據需求設置模塊的狀態,功能就是開啟/關閉某個模塊;
(2)在dtsi文件中,默認都是關閉模塊的,在開發板對應的dts文件中自己去打開需要的模塊;
clock:?clock@12010000?{ compatible?=?"hisilicon,hi3516dv300-clock"; #address-cells?=?; /*?表示reg里面的數據address占用一個字長*/ #size-cells?=?; /*?表示reg里面的數據size占用一個字長,注意字長不是字節*/ #clock-cells?=?; #reset-cells?=?; reg?=?; /*起始地址是0x12010000,長度是0x1000*/ };
-
reg屬性:配置某個硬件模塊對應的地址范圍信息;
-
#address-cells屬性:表示reg里面的數據address占用的字長,注意字長不是字節;
-
#size-cells:表示reg里面的數據size占用的字長,注意字長不是字節;
-
reg =
:address一般用來表示起始地址,length一般表示持續長度;
gic:?interrupt-controller@10300000?{ compatible?=?"arm,cortex-a7-gic"; #interrupt-cells?=?; /*表示interrupts用三個cell來描述中斷*/ #address-cells?=?; interrupt-controller; /*標明gic節點是中斷控制器*/ /*?gic?dist?base,?gic?cpu?base?,?no?virtual?support?*/ reg?=?,?; ?}; ipcm:?ipcm@045E0000?{ compatible?=?"hisilicon,ipcm-interrupt"; interrupt-parent?=?; /*父節點是gic節點*/ interrupts?=?; /**/ reg?=?; status?=?"okay"; };
(1)interrupt-controller:無值屬性,表示這是個中斷控制器node
(2)#interrupt-cells:這是中斷控制器節點的屬性,用來標識這個控制器需要幾個cell做中斷描述符
(3)interrupt-parent:標識此設備節點屬于哪一個中斷控制器,如果沒有這個屬性,會自動依附父節點
(4)interrupts :一個中斷標識符列表,表示每一個中斷輸出信號
6、特殊節點
chosen { stdout-path = "serial0:115200n8"; };
(1)chosen子節點不對應真實的設備,是用來描述內核啟動參數的,對應于uboot啟動內核時傳遞的bootargs參數;
(2)上面是摘抄的內核dts文件中的chosen子節點,里面只設置了stdout-path屬性,也就是把輸出設置成串口0,波特率是115200;
(3)dts文件中設置的屬性會被覆蓋點,具體就是uboot在啟動內核時,會將bootargs啟動參數轉換成chosen子節點的屬性,替換掉dts文件中設置的屬性;
~?#?ls?/proc/device-tree/chosen/ bootargs??name ~?#? ~?#?cat?/proc/device-tree/chosen/bootargs? mem=1408M?console=ttyS0,115200?root=/dev/mmcblk0p7?rootfstype=squashfs?rootwait ~?#? ~?#?cat?/proc/device-tree/chosen/name? chosen ~?#
aliases?{ serial0?=?&uart0; gpio0?=?&gpio_chip0; gpio1?=?&gpio_chip1; gpio2?=?&gpio_chip2; ······ };
aliases就是別名的意思,aliases節點主要功能就是給節點定義別名,為了方便訪問節點。不過我們在節點命名的時候可以加上label標簽,直接通過&label引用標簽來訪問也很方便,aliases節點內部其實也是通過引用標簽名來定義別名;
7、節點相關操作
gpio_chip1:?gpio_chip@120d1000?{ compatible?=?"arm,pl061",?"arm,primecell"; reg?=?; interrupts?=?; clocks?=?; clock-names?=?"apb_pclk"; #gpio-cells?=?; status?=?"disabled"; }; /*引用gpio_chip1節點*/ &gpio_chip1?{ status?=?"okay"; /*替換status屬性內容*/ };
對于已經定義好的節點,我們通過引用節點的方式,重新定義某些屬性,效果上看就是替換掉某些屬性的值;
/{ node{ key1=value1; } } /{ node{ key2=value2; } } //合并的結果 /{ node{ key1=value1; key2=value2; } }
有時候我們需要增加硬件描述的信息,這時候就可以在后面創新定義該節點,最后解析的時候會把同名節點不同的部分進行合并;
相關推薦:《Linux視頻教程》