目錄
前言
設備樹
設備樹概念
DTS、 DTB 和 DTC
DTS 語法
.dtsi 頭文件
設備節點
/根節點??
節點命名與標簽
節點層次結構?
屬性數據類型?
標準屬性
compatible 屬性
model 屬性
status 屬性
#address-cells 和#size-cells 屬性
reg 屬性
ranges 屬性
name 屬性
device_type 屬性
根節點 compatible 屬性
設備匹配過程
向節點追加或修改內容
前言
在新版本的 Linux 中, ARM 相關的驅動全部采用了設備樹,最新出的 CPU 其驅動開發也基本都是基于設備樹的,比如 ST 新出的 STM32MP157、NXP的 I.MX8系列等。
我們所使用的正點原子I.MX6UALPHA 開發板,Linux版本為 4.1.15,其支持設備樹,所以所有 Linux 驅動都是基于設備樹的。
本講實驗,我們了解一下設備樹的起源、重點學習一下設備樹語法。
設備樹
設備樹概念
設備樹(Device Tree),將這個詞分開就是“設備”和“樹”,描述設備樹的文件叫做 DTS(Device Tree Source)。
本質:硬件描述的“樹狀數據結構”??
- ??設備(Device)??:描述CPU、內存、外設等硬件單元。
- ??樹(Tree)??:以父子節點形式組織硬件關系,體現硬件拓撲。
- ??DTS(源文件)??:人類可讀的硬件描述文本,編譯后生成二進制DTB供內核使用。
DTS文件描述了開發板上的設備信息,比如CPU 數量、 內存基地址、 IIC 接口上接了哪些設備、 SPI 接口上接了哪些設備等等,如圖:
設備樹如何工作???
- 編寫DTS??:描述硬件連接(如UART地址、中斷號)。
- ??編譯為DTB??:通過DTC編譯器生成二進制DTB文件。
- 內核解析DTB??:啟動時,內核讀取DTB,動態生成設備節點(無需重新編譯內核)。
DTS、 DTB 和 DTC
設備樹源文件擴展名為.dts,但是我們在前面移植 Linux 的時候卻一直在使用.dtb 文件。
DTS 是設備樹源碼文件, DTB 是將 DTS 編譯以后得到的二進制文件。
將.c 文件編譯為.o 需要用到 gcc 編譯器,那么將.dts 編譯為.dtb需要用到 DTC 工具。
名稱 | 類型 | 用途 | 文件擴展名 | 編輯方式 |
---|---|---|---|---|
DTS | 文本文件 | 人類編寫硬件描述 |
| 文本編輯器 |
DTB | 二進制文件 | 內核直接讀取的硬件配置 |
| 不可直接編輯 |
DTC | 工具 | DTS 和 DTB 的轉換工具 | - | 命令行調用 |
?DTC 工具源碼在 Linux 內核的 scripts/dtc 目錄下。
scripts/dtc/Makefile 文件內容如下:
hostprogs-y := dtc
always := $(hostprogs-y)dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o \srcpos.o checks.o util.o
dtc-objs += dtc-lexer.lex.o dtc-parser.tab.o
......
可以看出, DTC 工具依賴于 dtc.c、 flattree.c、 fstree.c 等文件,最終編譯并鏈接出 DTC 這個主機文件。
如果要編譯 DTS 文件的話只需要進入到 Linux 源碼根目錄下,然后執行如下命令:
make all
make all”命令是編譯 Linux 源碼中的所有東西,包括 zImage, .ko 驅動模塊以及設備樹。
如果只是編譯設備樹的話建議使用以下命令:
make dtbs
每個板子都有一個對應的 DTS 文件,那么如何確定編譯哪一個 DTS 文件呢?
以 I.MX6ULL 這款芯片對應的板子為例,打開 arch/arm/boot/dts/Makefile, 在“ dtb- $(CONFIG_SOC_IMX6ULL)”配置項,有如下內容:
這個dtb就是我們之前移植Linux 系統的時候添加的設備樹,可以參考:系統移植篇20——Linux 內核移植(上)
當選中 I.MX6ULL 這個 SOC 以后(CONFIG_SOC_IMX6ULL=y),所有使用到I.MX6ULL 這個 SOC的板子對應的.dts 文件都會被編譯為.dtb。
如果我們使用 I.MX6ULL 新做了一個板子,只需要新建一個此板子對應的.dts 文件,然后將對應的.dtb 文件名添加到 dtb- $(CONFIG_SOC_IMX6ULL)下,這樣在編譯設備樹的時候就會將對應的.dts 編譯為二進制的.dtb文件。
DTS 語法
.dtsi 頭文件
和 C 語言一樣,設備樹也支持頭文件,設備樹的頭文件擴展名為.dtsi。
在 imx6ull-alientekemmc.dts 中有如下所示內容:
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
在imx6ull-14x14-evk-gpmi-weim.dts 這個文件里:
#include "imx6ull-14x14-evk.dts"
在.dts 設備樹文件中,可以通過“#include”來引用.h、 .dtsi 和.dts 文件。
這三個文件的關系:
我們在編寫設備樹頭文件的時候最好選擇.dtsi 后綴。
一般.dtsi 文件用于描述 SOC 的內部外設信息,比如 CPU 架構、主頻、外設寄存器地址范圍,比如 UART、 IIC 等等。
比如 imx6ull.dtsi 就是描述 I.MX6ULL 這顆 SOC 內部外設情況信息的,內容如下:
/* 包含必要的頭文件和設備樹片段 */
#include <dt-bindings/clock/imx6ul-clock.h> // i.MX6UL時鐘控制宏定義
#include <dt-bindings/gpio/gpio.h> // GPIO相關宏定義
#include <dt-bindings/interrupt-controller/arm-gic.h> // ARM中斷控制器宏
#include "imx6ull-pinfunc.h" // i.MX6ULL引腳功能定義
#include "imx6ull-pinfunc-snvs.h" // i.MX6ULL SNVS模塊引腳定義
#include "skeleton.dtsi" // 設備樹基礎框架/* 設備樹根節點 */
/ {/* 設備別名定義(方便驅動引用) */aliases {can0 = &flexcan1; // 將can0別名指向flexcan1節點/* 其他別名... */};/* CPU核心配置 */cpus {#address-cells = <1>; // 子節點地址用1個u32表示#size-cells = <0>; // 子節點不需要大小字段cpu0: cpu@0 {compatible = "arm,cortex-a7"; // CPU架構兼容性標識device_type = "cpu"; // 設備類型標識/* 其他CPU屬性... */};};/* 中斷控制器配置 */intc: interrupt-controller@00a01000 {compatible = "arm,cortex-a7-gic"; // ARM標準中斷控制器#interrupt-cells = <3>; // 中斷描述需要3個參數interrupt-controller; // 聲明為中斷控制器reg = <0x00a01000 0x1000>, // 寄存器地址范圍1<0x00a02000 0x100>; // 寄存器地址范圍2};/* 時鐘系統配置 */clocks {#address-cells = <1>;#size-cells = <0>;/* 32.768kHz低速時鐘 */ckil: clock@0 {compatible = "fixed-clock"; // 固定頻率時鐘reg = <0>; // 虛擬寄存器地址#clock-cells = <0>; // 無子時鐘clock-frequency = <32768>; // 時鐘頻率32.768kHzclock-output-names = "ckil"; // 時鐘輸出名稱};/* 其他時鐘... */};/* SoC外設總線 */soc {#address-cells = <1>; // 地址用1個u32表示#size-cells = <1>; // 大小用1個u32表示compatible = "simple-bus"; // 簡單內存映射總線interrupt-parent = <&gpc>; // 中斷父控制器ranges; // 地址轉換啟用/* 總線頻率控制節點 */busfreq {compatible = "fsl,imx_busfreq"; // NXP總線頻率控制驅動/* 其他屬性... */};/* NAND Flash控制器 */gpmi: gpmi-nand@01806000 {compatible = "fsl,imx6ull-gpmi-nand", "fsl,imx6ul-gpmi-nand"; // 兼容性#address-cells = <1>; // 子地址用1個u32表示#size-cells = <1>; // 子大小用1個u32表示reg = <0x01806000 0x2000>, // 寄存器區域1<0x01808000 0x4000>; // 寄存器區域2/* 其他NAND屬性... */};/* 其他外設... */};
};
在 imx6ull.dtsi 文件中,將 I.MX6ULL 這顆 SOC 所有的外設都描述的清清楚楚,比如 ecspi1~4、 uart1~8、 usbphy1~2、 i2c1~4等等。
設備節點
設備樹是采用樹形結構來描述板子上的設備信息的文件,每個設備都是一個節點,叫做設備節點,每個節點都通過一些屬性信息來描述節點信息,屬性就是鍵—值對。
以下是從imx6ull.dtsi 文件中縮減出來的設備樹文件內容:
/ {aliases {can0 = &flexcan1; // 別名:can0指向flexcan1節點};cpus {#address-cells = <1>;#size-cells = <0>;cpu0: cpu@0 { // 標簽cpu0,節點名cpu@0compatible = "arm,cortex-a7"; // 字符串屬性device_type = "cpu"; // 字符串屬性reg = <0>; // 32位整數屬性};};intc: interrupt-controller@00a01000 { // 標簽intccompatible = "arm,cortex-a7-gic";#interrupt-cells = <3>; // 中斷描述需要3個參數interrupt-controller; // 空屬性(布爾值)reg = <0x00a01000 0x1000>, // 地址范圍數組<0x00a02000 0x100>;};
};
/根節點??
- 每個 .dts/.dtsi文件必須有且僅有一個根節點 /。
- ??多文件合并規則??:若多個文件定義根節點,編譯時會將所有子節點合并到同一個根節點下(如 imx6ull.dtsi和 imx6ull-alientek-emmc.dts的根節點內容會合并)。
節點命名與標簽
標準命名格式
node-name@unit-address // 如 uart@40010000
- node-name:描述節點功能(如 uart、gpio)。
- unit-address:設備寄存器基地址(可省略,如 cpus節點)。
?標簽(Label)??
label: node-name@unit-address // 如 cpu0:cpu@0
- ??作用??:通過 &label快速引用節點(如 &cpu0代替 cpu@0)。
- ??優勢??:簡化長節點名的訪問(如 &intc代替 interrupt-controller@00a01000)。
節點層次結構?
可形成父子關系:
/ { // 根節點cpus { // 子節點cpu0: cpu@0 { ... }; // 子節點的子節點};
};
節點可嵌套,形成樹狀結構。
屬性數據類型?
每個節點都有不同屬性,不同的屬性又有不同的內容,屬性都是鍵值對,值可以為空或任意的字節流。
設備樹源碼中常用的幾種數據形式如下所示:
類型?? | ??示例?? | ??說明?? |
---|---|---|
??字符串?? |
| 用于驅動匹配的標識符。 |
??32位整數?? |
| 單個數值或寄存器地址。 |
??數組?? |
| 多值用空格分隔(如地址+長度)。 |
??字符串列表?? |
| 多個兼容性標識,逗號分隔。 |
標準屬性
節點是由一堆的屬性組成,節點都是具體的設備,不同的設備需要的屬性不同,有很多標準屬性,用戶可以自定義屬性。
常用的標準屬性如下:
compatible 屬性
compatible 屬性也叫做“兼容性”屬性:
- 核心作用:驅動與設備的“橋梁”??
- ??本質??:設備樹中用于??綁定設備與驅動??的關鍵屬性。
- ??工作原理??:內核啟動時,會遍歷設備樹中的 compatible值,與驅動中的 of_device_id表匹配,找到對應的驅動程序。
compatible 屬性的值格式如下所示:
compatible = "manufacturer,model", "manufacturer,generic-model";
內核按順序匹配,??第一個值??是設備專屬驅動(如板級定制),??第二個值??是通用驅動(如芯片廠商提供)。
以WM8960音頻芯片為例:
compatible = "fsl,imx6ul-evk-wm8960", "fsl,imx-audio-wm8960";
- fsl,imx6ul-evk-wm8960:飛思卡爾為開發板定制的驅動。
- fsl,imx-audio-wm8960:飛思卡爾提供的WM8960通用驅動。
一般驅動程序文件都會有一個 OF 匹配表,此 OF 匹配表保存著一些 compatible 值,如果設備節點的 compatible 屬性值和 OF 匹配表中的任何一個值相等,那么就表示設備可以使用這個驅動。
比如在文件 imx-wm8960.c 中有如下內容:
/* 定義設備樹兼容性匹配表 */
static const struct of_device_id imx_wm8960_dt_ids[] = {{ .compatible = "fsl,imx-audio-wm8960", }, // 匹配設備樹中兼容"fsl,imx-audio-wm8960"的節點{ /* sentinel */ } // 結束標記
};/* 向內核注冊設備樹匹配表 */
MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);/* 定義平臺驅動結構體 */
static struct platform_driver imx_wm8960_driver = {.driver = {.name = "imx-wm8960", // 驅動名稱.pm = &snd_soc_pm_ops, // 電源管理操作集(使用標準ASoC電源管理操作).of_match_table = imx_wm8960_dt_ids, // 指向設備樹匹配表},.probe = imx_wm8960_probe, // 設備探測函數,當匹配到設備時調用.remove = imx_wm8960_remove, // 設備移除函數,當設備斷開時調用
};
數組 imx_wm8960_dt_ids 就是 imx-wm8960.c 這個驅動文件的匹配表,此匹配表只有一個匹配值“fsl,imx-audio-wm8960”。如果在設備樹中有哪個節點的 compatible 屬性值與此相等,那么這個節點就會使用此驅動文件。
wm8960 采用了 platform_driver 驅動模式,設置.of_match_table 為 imx_wm8960_dt_ids,也就是設置這個 platform_driver 所使用的OF 匹配表。
model 屬性
model 屬性值也是一個字符串,一般 model 屬性描述設備模塊信息,比如:
model = "wm8960-audio";
status 屬性
status屬性是設備樹(Device Tree)中用于描述設備當前狀態的屬性,通常由操作系統或固件在運行時解析,以決定是否初始化或使用該設備。
可選的狀態如表:
#address-cells 和#size-cells 屬性
這兩個屬性用于??描述設備樹中子節點的地址和大小信息??,通常出現在??有子節點的設備節點??中,用于定義子節點 reg屬性的解析方式。
屬性?? | ??數據類型?? | ??作用?? |
---|---|---|
|
| 指定子節點? |
|
| 指定子節點? |
- 字長(cell)??:1 cell = 32 bits(4字節)。
- ??影響范圍??:這兩個屬性僅影響??當前節點的子節點??,不會影響兄弟節點或父節點。
舉例:
/* SPI4 控制器節點,使用 GPIO 模擬 SPI */
spi4 {compatible = "spi-gpio"; // 使用 GPIO 模擬 SPI 總線#address-cells = <1>; // 子節點 reg 屬性中的地址部分占 1 個 32 位字#size-cells = <0>; // 子節點 reg 屬性不包含大小信息/* SPI 設備節點:74HC595 移位寄存器 */gpio_spi: gpio_spi@0 {compatible = "fairchild,74hc595"; // 設備兼容性標識reg = <0>; // 設備片選號(CS),由于 #size-cells=0,只包含地址部分};
};/* AIPS3 總線節點 */
aips3: aips-bus@02200000 {compatible = "fsl,aips-bus", "simple-bus"; // AIPS 總線兼容性標識#address-cells = <1>; // 子節點 reg 屬性中的地址部分占 1 個 32 位字#size-cells = <1>; // 子節點 reg 屬性中的大小部分占 1 個 32 位字/* DCP 加密模塊節點 */dcp: dcp@02280000 {compatible = "fsl,imx6sl-dcp"; // i.MX6SL 的 DCP 模塊reg = <0x02280000 0x4000>; // 寄存器基地址 0x02280000,大小 16KB};
};
reg 屬性
reg 屬性一般用于描述設備地址空間資源信息,一般都是某個外設的寄存器地址范圍信息。
格式如下:
reg = <address1 length1 address2 length2 ...>;
- address??:起始地址(由 #address-cells決定占用的 cell 數量)。
- ??length??:地址范圍長度(由 #size-cells決定占用的 cell 數量)。
舉例:
soc {#address-cells = <1>;#size-cells = <1>;ethernet@10000000 {reg = <0x10000000 0x1000>; // 起始地址 0x10000000,長度 4KB};
};
ranges 屬性
ranges是設備樹(Device Tree)中用于??描述子節點地址空間如何映射到父節點地址空間??的關鍵屬性,本質是一個??地址轉換表??。其核心作用是定義??子總線地址??與??父總線地址??之間的映射關系。
每個 ranges條目由 ??3 部分組成??,格式如下:
ranges = <child-addr parent-addr length>;
每個條目表示一段地址空間的映射關系:
??字段?? | ??描述?? | ??長度由誰決定?? |
---|---|---|
?? | 子總線地址空間的物理地址 | 父節點的? |
?? | 父總線地址空間的物理地址 | 父節點的? |
?? | 子地址空間的長度(范圍) | 父節點的? |
舉例:
/* 空 ranges(無需轉換) */soc {#address-cells = <1>;#size-cells = <1>;ranges; // 子地址 = 父地址,無需轉換uart1: serial@02020000 {reg = <0x02020000 0x4000>; // 地址 0x02020000 直接對應 CPU 物理地址};
};/* 非空 ranges(地址轉換) */pcie {#address-cells = <2>; // 子地址 64 位#size-cells = <1>; // 長度 32 位ranges = <0x00 0x00000000 0x80000000 0x10000000>; // 子地址 0x0_00000000 → 父地址 0x80000000,長度 256MBdevice@0 {reg = <0x00 0x00000000 0x10000000>; // 子設備地址范圍};
};
name 屬性
name 屬性值為字符串, name 屬性用于記錄節點名字。
name 屬性已經被棄用,不推薦使用name 屬性,一些老的設備樹文件可能會使用此屬性。
device_type 屬性
device_type 屬性值為字符串, IEEE 1275 會用到此屬性,用于描述設備的 FCode,但是設備樹沒有 FCode,所以此屬性也被拋棄了。此屬性只能用于 cpu 節點或者 memory 節點。i
mx6ull.dtsi 的 cpu0 節點用到了此屬性,內容如下所示:
cpu0: cpu@0 {compatible = "arm,cortex-a7";device_type = "cpu";reg = <0>;
......
};
根節點 compatible 屬性
每個節點都有 compatible 屬性,根節點“/”也不例外,
imx6ull-alientek-emmc.dts 文件中根節點的 compatible 屬性內容如下所示:
/ {model = "Freescale i.MX6 ULL 14x14 EVK Board";compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
......
}
設備節點的 compatible 屬性值是為了匹配 Linux 內核中的驅動程序,compatible 有兩個值:“fsl,imx6ull-14x14-evk”和“fsl,imx6ull”。
Linux 內核會通過根節點的 compoatible 屬性查看是否支持此設備,如果支持的話設備就會啟動 Linux 內核。
設備匹配過程
Linux 內核通過根節點 compatible 屬性找到對應的設備的函數調用過程如圖:
1) 入口:start_kernel(初始化內核)??
- 位置:init/main.c
- 調用?setup_arch()進入架構相關初始化。
??(2) 解析設備樹:setup_arch??
- 位置:arch/arm/kernel/setup.c
- 調用?setup_machine_fdt()匹配設備樹根節點的?compatible。
??(3) 匹配?machine_desc:setup_machine_fdt??
- 位置:arch/arm/kernel/devtree.c
關鍵步驟:
- 通過?of_flat_dt_match_machine()匹配設備樹根節點的?compatible。
- 返回匹配的?machine_desc結構體。
??(4) 匹配邏輯:of_flat_dt_match_machine??
- 位置:drivers/of/fdt.c
- 遍歷內核預定義的?machine_desc列表(通過?DT_MACHINE_START宏注冊),與設備樹的?compatible字符串比較。
- 匹配優先級:??完全匹配 > 部分匹配??(按?compatible列表順序)。
??(5) 返回?machine_desc??
- 匹配成功后,內核會執行?machine_desc中定義的平臺初始化函數(如?init_machine)。
向節點追加或修改內容
產品開發過程中可能面臨著頻繁的需求更改,怎么修改或者新增內容呢?
方法一,修改通用SOC文件,比如imx6ull.dtsi 文件:
原始代碼如下:
i2c1: i2c@021a0000 {#address-cells = <1>; // 子節點 reg 地址字段占 1 個 cell(32位)#size-cells = <0>; // 子節點 reg 不包含長度字段compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; // 驅動兼容性標識reg = <0x021a0000 0x4000>; // 寄存器基地址 0x021a0000,長度 16KBinterrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; // 中斷號 36,高電平觸發clocks = <&clks IMX6UL_CLK_I2C1>; // 時鐘源引用status = "disabled"; // 默認禁用,需在板級 DTS 中啟用
};
現在要在 i2c1 節點下創建一個子節點,這個子節點就是 fxls8471,最簡單的方法就是在 i2c1 下直接添加一個名為 fxls8471 的子節點,如下所示:
i2c1: i2c@021a0000 {#address-cells = <1>; // 子設備地址用1個32位單元表示#size-cells = <0>; // 子設備不需要地址空間大小compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; // 兼容的驅動程序reg = <0x021a0000 0x4000>; // 控制器寄存器地址范圍interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; // 中斷配置clocks = <&clks IMX6UL_CLK_I2C1>; // 時鐘源status = "disabled"; // 默認禁用控制器/* FXLS8471加速度傳感器子節點 */fxls8471@1e {compatible = "fsl,fxls8471"; // 設備驅動兼容性reg = <0x1e>; // I2C設備地址0x1E};
};
這樣非常簡單,但是imx6ull.dtsi 是設備樹頭文件,其他所有使用到 I.MX6ULL這顆 SOC 的板子都會引用 imx6ull.dtsi 這個文件。
直接在 i2c1 節點中添加 fxls8471 就相當于在其他的所有板子上都添加了 fxls8471 這個設備,但是不是每個板子都有該設備。
所以方法二,在修改板級設備樹文件中,追加設備:
I.MX6U-ALPHA 開發板使用的設備樹文件為 imx6ull-alientek-emmc.dts,因此我們需要在imx6ull-alientek-emmc.dts 文件中完成數據追加的內容,方式如下:
&i2c1 {// 添加 FXLS8471 傳感器子節點fxls8471@1e {compatible = "nxp,fxls8471"; // 驅動匹配字符串reg = <0x1e>; // I2C 設備地址interrupt-parent = <&gpio5>; // 中斷引腳配置interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;};
};
修改imx6ull-alientek-emmc.dts 這個文件內的代碼,不會對使用 I.MX6ULL 這顆 SOC 的其他板子造成任何影響。
這個就是向節點追加或修改內容的方法,重點就是通過&label 來訪問節點,然后直接在里面編寫要追加或者修改的內容。