设备树的引入及简明教程
文章目錄
- 1 設備樹的引入與作用
- 2 設備樹的語法
- 2.1 設備樹語法概覽
- 2.2 Devicetree 格式
- 2.2.1 DTS 文件的格式
- 2.2.2 node 的格式
- 2.2.3 properties 的格式
- 2.3 dts 文件包含 dtsi 文件
- 2.4 常用屬性
- 2.4.1 #address-cells、#size-cells
- 2.4.2 compatible
- 2.4.3 model
- 2.4.4 status
- 2.4.5 reg
- 2.4.6 name(過時了,建議不用)
- 2.4.7 device_type(過時了,建議不用)
- 2.5 常用的屬性
- 2.5.1 根節點
- 2.5.2 CPU 節點
- 2.5.3 memory 節點
- 2.5.4 chosen 節點
- 3 編譯、更換設備樹
- 3.1 在內核中直接 make
- 3.2 手工編譯
- 3.3 給開發板更換設備樹文件
1 設備樹的引入與作用
以 LED 驅動為例,如果你要更換 LED 所用的 GPIO 引腳,需要修改驅動程序源碼、重新編譯驅動、重新加載驅動。在內核中,使用同一個芯片的板子,它們所用的外設資源不一樣,比如 A 板用 GPIO A,B 板用 GPIO B。而 GPIO 的驅動程序既支持 GPIO A 也支持 GPIO B,你需要指定使用哪一個引腳,怎么指定?在 c 代碼中指定。
隨著 ARM 芯片的流行,內核中針對這些 ARM 板保存有大量的、沒有技術含量的文件。Linus 大發雷霆:“this whole ARM thing is a f*cking pain in the ass”。于是,Linux 內核開始引入設備樹。設備樹并不是重新發明出來的,在 Linux 內核中其他平臺如 PowerPC,早就使用設備樹來描述硬件了。Linus 發火之后,內核開始全面使用設備樹來改造,神人就神人。
有一種錯誤的觀點,說“新驅動都是用設備樹來寫了”。設備樹不可能用來寫驅動。請想想,要操作硬件就需要去操作復雜的寄存器,如果設備樹可以操作寄存器,那么它就是“驅動”,它就一樣很復雜。
設備樹只是用來給內核里的驅動程序,指定硬件的信息。比如 LED 驅動,在內核的驅動程序里去操作寄存器,但是操作哪一個引腳?這由設備樹指定。
你可以事先體驗一下設備樹,板子啟動后執行下面的命令:
ls /sys/firmware/
devicetree fdt
/sys/firmware/devicetree 目錄下是以目錄結構程現的 dtb 文件, 根節點對應 base 目錄, 每一個節點對應一個目錄, 每一個屬性對應一個文件。
這些屬性的值如果是字符串,可以使用 cat 命令把它打印出來;對于數值,可以用 hexdump 把它打印出來。
一個單板啟動時,u-boot 先運行,它的作用是啟動內核。U-boot 會把內核和設備樹文件都讀入內存,然后啟動內核。在啟動內核時會把設備樹在內存中的地址告訴內核。
2 設備樹的語法
2.1 設備樹語法概覽
為什么叫“樹”?
怎么描述這棵樹?
我們需要編寫設備樹文件(dts: device tree source),它需要編譯為 dtb(device tree blob)文件,內核使用的是 dtb 文件。dts 文件是根本,它的語法很簡單。
下面是一個設備樹示例:
它對應的 dts 文件如下:
2.2 Devicetree 格式
2.2.1 DTS 文件的格式
DTS 文件布局(layout):
/dts-v1/; // 表示版本 [memory reservations] // 格式為: /memreserve/ <address> <length>; / {[property definitions][child nodes] };2.2.2 node 的格式
設備樹中的基本單元,被稱為“node”,其格式為:
[label:] node-name[@unit-address] {[properties definitions][child nodes] };label 是標號,可以省略。label 的作用是為了方便地引用 node,比如:
/dts-v1/; / { uart0: uart@fe001000 {compatible="ns16550";reg=<0xfe001000 0x100>; }; };可以使用下面 2 種方法來修改 uart@fe001000 這個 node:
// 在根節點之外使用 label 引用 node: &uart0 {status = “disabled”; }; // 或在根節點之外使用全路徑: &{/uart@fe001000} {status = “disabled”; };2.2.3 properties 的格式
簡單地說,properties 就是“name=value”,value 有多種取值方式。
Property 格式 1:
[label:] property-name = value;
Property 格式 2(沒有值):
[label:] property-name;
Property 取值只有 3 種:
- arrays of cells(1 個或多個 32 位數據, 64 位數據使用 2 個 32 位數據表示)
- string(字符串),
- bytestring(1 個或多個字節)
示例:
a. Arrays of cells : cell 就是一個 32 位的數據,用尖括號包圍起來interrupts = <17 0xc>;
b. 64bit 數據使用 2 個 cell 來表示,用尖括號包圍起來:clock-frequency = <0x00000001 0x00000000>;
c. A null-terminated string (有結束符的字符串),用雙引號包圍起來:compatible = "simple-bus";
d. A bytestring(字節序列) ,用中括號包圍起來:
local-mac-address = [00 00 12 34 56 78]; // 每個 byte 使用 2 個16 進制數來表示
local-mac-address = [000012345678]; // 每個 byte 使用 2 個 16進制數來表示
e. 可以是各種值的組合, 用逗號隔開:
compatible = "ns16550", "ns8250";
example = <0xf00f0000 19>, "a strange property format";
2.3 dts 文件包含 dtsi 文件
設備樹文件不需要我們從零寫出來,內核支持了某款芯片比如 imx6ull,在內核的 arch/arm/boot/dts目錄下就有了能用的設備樹模板,一般命名為 xxxx.dtsi。“i”表示“include”,被別的文件引用的。
我們使用某款芯片制作出了自己的單板,所用資源跟 xxxx.dtsi 是大部分相同,小部分不同,所以需要引腳 xxxx.dtsi 并修改。dtsi 文件跟 dts 文件的語法是完全一樣的。dts 中可以包含.h 頭文件,也可以包含 dtsi 文件,在.h 頭文件中可以定義一些宏。
示例:
/dts-v1/; #include <dt-bindings/input/input.h> #include "imx6ull.dtsi" / {// …… };2.4 常用屬性
2.4.1 #address-cells、#size-cells
cell 指一個 32 位的數值,address-cells:address 要用多少個 32 位數來表示;size-cells:size 要用多少個 32 位數來表示。
比如一段內存,怎么描述它的起始地址和大小?
下例中,address-cells 為 1,所以 reg 中用 1 個數來表示地址,即用 0x80000000 來表示地址;size cells 為 1,所以 reg 中用 1 個數來表示大小,即用 0x20000000 表示大小:
/ { #address-cells = <1>; #size-cells = <1>; memory { reg = <0x80000000 0x20000000>;}; };2.4.2 compatible
“compatible”表示“兼容”,對于某個 LED,內核中可能有 A、B、C 三個驅動都支持它,那可以這樣
寫:
內核啟動時,就會為這個 LED 按這樣的優先順序為它找到驅動程序:A、B、C。
根節點下也有 compatible 屬性,用來選擇哪一個“machine desc”:一個內核可以支持 machine A,也支持 machine B,內核啟動后會根據根節點的 compatible 屬性找到對應的 machine desc 結構體,執行其中的初始化函數。
compatible 的值,建議取這樣的形式:“manufacturer,model”,即“廠家名,模塊名”。
注意:machine desc 的意思就是“機器描述”,學到內核啟動流程時才涉及。
2.4.3 model
model 屬性與 compatible 屬性有些類似,但是有差別。compatible 屬性是一個字符串列表,表示可以你的硬件兼容 A、B、C 等驅動;model 用來準確地定義這個硬件是什么。
比如根節點中可以這樣寫:
/ { compatible = "samsung,smdk2440", "samsung,mini2440"; model = "jz2440_v3"; };它表示這個單板,可以兼容內核中的“smdk2440”,也兼容“mini2440”。從 compatible 屬性中可以知道它兼容哪些板,但是它到底是什么板?用 model 屬性來明確。
2.4.4 status
dtsi 文件中定義了很多設備,但是在你的板子上某些設備是沒有的。這時你可以給這個設備節點添加一個 status 屬性,設置為“disabled”:
&uart1 {status = "disabled"; };2.4.5 reg
reg 的本意是 register,用來表示寄存器地址。但是在設備樹里,它可以用來描述一段空間。反正對于 ARM 系統,寄存器和內存是統一編址的,即訪問寄存器時用某塊地址,訪問內存時用某塊地址,在訪問方法上沒有區別。
reg 屬性的值,是一系列的“address size”,用多少個 32 位的數來表示 address 和 size,由其父節點的#address-cells、#size-cells 決定。
示例:
/dts-v1/; / { #address-cells = <1>; #size-cells = <1>; memory { reg = <0x80000000 0x20000000>; }; };2.4.6 name(過時了,建議不用)
它的值是字符串,用來表示節點的名字。在跟 platform_driver 匹配時,優先級最低。
compatible 屬性在匹配過程中,優先級最高。
2.4.7 device_type(過時了,建議不用)
它的值是字符串,用來表示節點的類型。在跟 platform_driver 匹配時,優先級為中。
compatible 屬性在匹配過程中,優先級最高。
2.5 常用的屬性
2.5.1 根節點
dts 文件中必須有一個根節點:
/dts-v1/; / { model = "SMDK24440"; compatible = "samsung,smdk2440"; #address-cells = <1>; #size-cells = <1>; };根節點中必須有這些屬性:
#address-cells // 在它的子節點的 reg 屬性中, 使用多少個 u32 整數來描述地址(address) #size-cells // 在它的子節點的 reg 屬性中, 使用多少個 u32 整數來描述大小(size) compatible // 定義一系列的字符串, 用來指定內核中哪個 machine_desc 可以支持本設備// 即這個板子兼容哪些平臺// uImage : smdk2410 smdk2440 mini2440 ==> machine_descmodel // 咱這個板子是什么// 比如有 2 款板子配置基本一致, 它們的 compatible 是一樣的// 那么就通過 model 來分辨這 2 款板子2.5.2 CPU 節點
一般不需要我們設置,在 dtsi 文件中都定義好了:
cpus { #address-cells = <1>; #size-cells = <0>; cpu0: cpu@0 {.......} };2.5.3 memory 節點
芯片廠家不可能事先確定你的板子使用多大的內存,所以 memory 節點需要板廠設置,比如:
memory { reg = <0x80000000 0x20000000>; };2.5.4 chosen 節點
我們可以通過設備樹文件給內核傳入一些參數,這要在 chosen 節點中設置 bootargs 屬性:
chosen { bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200"; };3 編譯、更換設備樹
我們一般不會從零寫 dts 文件,而是修改。程序員水平有高有低,改得對不對?需要編譯一下。并且內
核直接使用 dts 文件的話,就太低效了,它也需要使用二進制格式的 dtb 文件。
3.1 在內核中直接 make
設置 ARCH、CROSS_COMPILE、PATH 這三個環境變量后,進入 ubuntu 上板子內核源碼的目錄,執行如下命令即可編譯 dtb 文件:
make dtbs V=1
以野火的 IMX6UL 為例,可以看到如下輸出:
mkdir -p arch/arm/boot/dts/ ; arm-linux-gnueabihf-gcc -E -Wp,-MD,arch/arm/boot/dts/.imx6ull-14x14-ebf-mini.dtb.d.pre.tmp -nostdinc-I./arch/arm/boot/dts -I./arch/arm/boot/dts/include -I./drivers/of/testcase-data -undef -D__DTS__ -x assembler-with-cpp -o arch/arm/boot/dts/.imx6ull-14x14-ebf-mini.dtb.dts.tmp arch/arm/boot/dts/imx6ull-14x14-ebf-mini.dts ; ./scripts/dtc/dtc -O dtb -o arch/arm/boot/dts/imx6ull-14x14-ebf-mini.dtb-b 0 -i arch/arm/boot/dts/ -Wno-unit_address_vs_reg -d arch/arm/boot/dts/.imx6ull-14x14-ebf-mini.dtb.d.dtc.tmp arch/arm/boot/dts/.imx6ull-14x14-ebf-mini.dtb.dts.tmp ;它首先用 arm-linux-gnueabihf-gcc 預處理 dts 文件,把其中的.h 頭文件包含進來,把宏展開。然后使用 scripts/dtc/dtc 生成 dtb 文件。
可見,dts 文件之所以支持“#include”語法,是因為 arm-linux-gnueabihf-gcc 幫忙。
如果只用 dtc 工具,它是不支持”#include”語法的,只支持“/include”語法。
3.2 手工編譯
除非你對設備樹比較了解,否則不建議手工使用 dtc 工具直接編譯。
內核目錄下 scripts/dtc/dtc 是設備樹的編譯工具,直接使用它的話,包含其他文件時不能使用“#include”,而必須使用“/incldue”。
編譯、反編譯的示例命令如下,“-I”指定輸入格式,“-O”指定輸出格式,“-o”指定輸出文件:
./scripts/dtc/dtc -I dts -O dtb -o tmp.dtb arch/arm/boot/dts/xxx.dts // 編譯 dts 為 dtb
./scripts/dtc/dtc -I dtb -O dts -o tmp.dts arch/arm/boot/dts/xxx.dtb // 反編譯 dtb 為 dts
3.3 給開發板更換設備樹文件
對于 100ask-am335x 單板 :
設備樹文件是:內核源碼目錄中 arch/arm/boot/dts/100ask-am335x.dtb,要更換板子上的設備樹文件,啟動板子后,更換這個文件:/boot/100ask-am335x.dtb。
總結
以上是生活随笔為你收集整理的设备树的引入及简明教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: u盘启动盘制作完后怎么用 如何使用制作好
- 下一篇: 海尔bios怎么设置u盘启动不了系统 海