裝置樹學習

Bathwind_W發表於2024-06-07

裝置樹(Device Tree),將這個詞分開就是“裝置”和“樹”,描述裝置樹的檔案叫做 DTS(DeviceTree Source),這個 DTS 檔案採用樹形結構描述板級裝置,也就是開發板上的裝置資訊,比如CPU 數量、 記憶體基地址、 IIC 介面上接了哪些裝置、 SPI 介面上接了哪些裝置等等。具體如下圖所示:

在這裡插入圖片描述

樹的主幹就是系統匯流排, IIC 控制器、 GPIO 控制器、 SPI 控制器等都是接到系統主線上的分支。

DTS、 DTB 和 DTC分析

裝置樹原始檔副檔名為.dts,但是我們在前面移植 Linux 的時候卻一直在使用.dtb 檔案,那麼 DTS 和 DTB 這兩個檔案是什麼關係呢? DTS 是裝置樹原始碼檔案, DTB 是將DTS 編譯以後得到的二進位制檔案。將.c 檔案編譯為.o 需要用到 gcc 編譯器,那麼將.dts 編譯為.dtb需要用到 DTC 工具。

示例程式碼 43.2.1 scripts/dtc/Makefile 檔案程式碼段
1 hostprogs-y := dtc
2 always := $(hostprogs-y)
3 4
dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o \
5 srcpos.o checks.o util.o
6 dtc-objs += dtc-lexer.lex.o dtc-parser.tab.o

DTC 工具依賴於 dtc.c、 flattree.c、 fstree.c 等檔案,最終編譯並連結出 DTC 這個主機檔案。如果要編譯 DTS 檔案的話只需要進入到 Linux 原始碼根目錄下,然後執行如下命令:

make all

或者:

make dtbs

make all會編譯整個linux核心,而make dtbs只會編譯裝置樹檔案。
每個板子都有一個對應的 DTS 檔案,那麼如何確定編譯哪一個 DTS 檔案呢?

示例程式碼 43.2.2 arch/arm/boot/dts/Makefile 檔案程式碼段
381 dtb-$(CONFIG_SOC_IMX6UL) += \
382 imx6ul-14x14-ddr3-arm2.dtb \
383 imx6ul-14x14-ddr3-arm2-emmc.dtb \
......
400 dtb-$(CONFIG_SOC_IMX6ULL) += \
401 imx6ull-14x14-ddr3-arm2.dtb \
402 imx6ull-14x14-ddr3-arm2-adc.dtb \
403 imx6ull-14x14-ddr3-arm2-cs42888.dtb \
404 imx6ull-14x14-ddr3-arm2-ecspi.dtb \
405 imx6ull-14x14-ddr3-arm2-emmc.dtb \
406 imx6ull-14x14-ddr3-arm2-epdc.dtb \
407 imx6ull-14x14-ddr3-arm2-flexcan2.dtb \
408 imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb \
409 imx6ull-14x14-ddr3-arm2-lcdif.dtb \
410 imx6ull-14x14-ddr3-arm2-ldo.dtb \
411 imx6ull-14x14-ddr3-arm2-qspi.dtb \
412 imx6ull-14x14-ddr3-arm2-qspi-all.dtb \
413 imx6ull-14x14-ddr3-arm2-tsc.dtb \
414 imx6ull-14x14-ddr3-arm2-uart2.dtb \
415 imx6ull-14x14-ddr3-arm2-usb.dtb \
416 imx6ull-14x14-ddr3-arm2-wm8958.dtb \
417 imx6ull-14x14-evk.dtb \
418 imx6ull-14x14-evk-btwifi.dtb \
419 imx6ull-14x14-evk-emmc.dtb \
420 imx6ull-14x14-evk-gpmi-weim.dtb \
421 imx6ull-14x14-evk-usb-certi.dtb \
422 imx6ull-alientek-emmc.dtb \
423 imx6ull-alientek-nand.dtb \
424 imx6ull-9x9-evk.dtb \
425 imx6ull-9x9-evk-btwifi.dtb \
426 imx6ull-9x9-evk-ldo.dtb
427 dtb-$(CONFIG_SOC_IMX6SLL) += \
428 imx6sll-lpddr2-arm2.dtb \
429 imx6sll-lpddr3-arm2.dtb \
......

當選中 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 中有如下所示內容:

示例程式碼 43.3.1.1 imx6ull-alientek-emmc.dts 檔案程式碼段
12 #include <dt-bindings/input/input.h>
13 #include "imx6ull.dtsi"

在.dts 裝置樹檔案中,可以透過“#include”來引用.h、 .dtsi 和.dts 檔案。只是,我們在編寫裝置樹標頭檔案的時候最好選擇.dtsi 字尾。一般.dtsi 檔案用於描述 SOC 的內部外設資訊,比如 CPU 架構、主頻、外設暫存器地址範圍,比如 UART、 IIC 等等。比如 imx6ull.dtsi 就是描述 I.MX6ULL 這顆 SOC 內部外設情況資訊的,內容如下:

示例程式碼 43.3.1.3 imx6ull.dtsi 檔案程式碼段
10 #include <dt-bindings/clock/imx6ul-clock.h>
11 #include <dt-bindings/gpio/gpio.h>
12 #include <dt-bindings/interrupt-controller/arm-gic.h>
13 #include "imx6ull-pinfunc.h"
14 #include "imx6ull-pinfunc-snvs.h"
15 #include "skeleton.dtsi"
16
17 / {
18 aliases {
19 can0 = &flexcan1;
......
48 };
49
50 cpus {
51 #address-cells = <1>;
52 #size-cells = <0>;
53
54 cpu0: cpu@0 {
55 compatible = "arm,cortex-a7";
56 device_type = "cpu";
......
89 };
90 };
91
92 intc: interrupt-controller@00a01000 {
93 compatible = "arm,cortex-a7-gic";
94 #interrupt-cells = <3>;
95 interrupt-controller;
96 reg = <0x00a01000 0x1000>,
97 <0x00a02000 0x100>;
98 };
99
100 clocks {
101 #address-cells = <1>;
102 #size-cells = <0>;
103
104 ckil: clock@0 {
105 compatible = "fixed-clock";
106 reg = <0>;
107 #clock-cells = <0>;
108 clock-frequency = <32768>;
109 clock-output-names = "ckil";
110 };
......
135 };
136
137 soc {
138 #address-cells = <1>;
139 #size-cells = <1>;
140 compatible = "simple-bus";
141 interrupt-parent = <&gpc>;
142 ranges;
143
144 busfreq {
145 compatible = "fsl,imx_busfreq";
......
162 };
197
198 gpmi: gpmi-nand@01806000{
199 compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpminand";
200 #address-cells = <1>;
201 #size-cells = <1>;
202 reg = <0x01806000 0x2000>, <0x01808000 0x4000>;
......
216 };
......
1177 };
1178 };

54~89 行就是 cpu0 這個裝置節點資訊,這個節點資訊描述了I.MX6ULL 這顆 SOC 所使用的 CPU 資訊,比如架構是 cortex-A7,頻率支援 996MHz、 792MHz、528MHz、396MHz 和 198MHz 等等。

裝置節點

裝置樹是採用樹形結構來描述板子上的裝置資訊的檔案,每個裝置都是一個節點,叫做裝置節點,每個節點都透過一些屬性資訊來描述節點資訊,屬性就是鍵—值對。以下是從imx6ull.dtsi 檔案中縮減出來的裝置樹檔案內容:

示例程式碼 43.3.2.1 裝置樹模板
1 / {
2 aliases {
3 can0 = &flexcan1;
4 };
5 6
cpus {
7 #address-cells = <1>;
8 #size-cells = <0>;
9
10 cpu0: cpu@0 {
11 compatible = "arm,cortex-a7";
12 device_type = "cpu";
13 reg = <0>;
14 };
15 };
16
17 intc: interrupt-controller@00a01000 {
18 compatible = "arm,cortex-a7-gic";
19 #interrupt-cells = <3>;
20 interrupt-controller;
21 reg = <0x00a01000 0x1000>,
22 <0x00a02000 0x100>;
23 };
24 }

第 2、 6 和 17 行, aliases、 cpus 和 intc 是三個子節點,在裝置樹中節點命名格式如下:node-name@unit-address其中“node-name”是節點名字,為 ASCII 字串,節點名字應該能夠清晰的描述出節點的功能,比如“uart1”就表示這個節點是 UART1 外設。“unit-address”一般表示裝置的地址或暫存器首地址,如果某個節點沒有地址或者暫存器的話“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。
cpu0:cpu@0上述命令並不是“node-name@unit-address”這樣的格式,而是用“:”隔開成了兩部分,“:”
前面的是節點標籤(label),“:”後面的才是節點名字,
abel: node-name@unit-address
引入 label 的目的就是為了方便訪問節點,可以直接透過&label 來訪問這個節點,比如透過&cpu0 就可以訪問“cpu@0”這個節點,而不需要輸入完整的節點名字。再比如節點 “intc:interrupt-controller@00a01000”,節點 label 是 intc,而節點名字就很長了,為“ interruptcontroller@00a01000”。很明顯透過&intc 來訪問“interrupt-controller@00a01000”這個節點要方便很多!
第 10 行, cpu0 也是一個節點,只是 cpu0 是 cpus 的子節點
裝置樹原始碼中常用的幾種資料形式如下所示:

①、字串
compatible = "arm,cortex-a7";
上述程式碼設定 compatible 屬性的值為字串“arm,cortex-a7”。
②、 32 位無符號整數
reg = <0>;
上述程式碼設定 reg 屬性的值為 0, reg 的值也可以設定為一組值,比如:
reg = <0 0x123456 100>;
③、字串列表
屬性值也可以為字串列表,字串和字串之間採用“,”隔開,如下所示:
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";

標準屬性

1、 compatible 屬性

compatible 屬性也叫做“相容性”屬性,這是非常重要的一個屬性! compatible 屬性的值是一個字串列表, compatible 屬性用於將裝置和驅動繫結起來。字串列表用於選擇裝置所要使用的驅動程式, compatible 屬性的值格式如下所示:

"manufacturer,model"

其中 manufacturer 表示廠商, model 一般是模組對應的驅動名字。比如 imx6ull-alientekemmc.dts 中 sound 節點是 I.MX6U-ALPHA 開發板的音訊裝置節點, I.MX6U-ALPHA 開發板上的音訊晶片採用的歐(WOLFSON)出品的 WM8960, sound 節點的 compatible 屬性值如下:

compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";

sound這個裝置首先使用第一個相容值在 Linux 核心裡面查詢,看看能不能找到與之匹配的驅動檔案,如果沒有找到的話就使用第二個相容值查。一般驅動程式檔案都會有一個 OF 匹配表,此 OF 匹配表儲存著一些compatible 值,如果裝置節點的 compatible 屬性值和 OF 匹配表中的任何一個值相等,那麼就表示裝置可以使用這個驅動。

示例程式碼 43.3.3.1 imx-wm8960.c 檔案程式碼段
632 static const struct of_device_id imx_wm8960_dt_ids[] = {
633 { .compatible = "fsl,imx-audio-wm8960", },
634 { /* sentinel */ }
635 };
636 MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
637
638 static struct platform_driver imx_wm8960_driver = {
639 .driver = {
640 .name = "imx-wm8960",
641 .pm = &snd_soc_pm_ops,
642 .of_match_table = imx_wm8960_dt_ids,
643 },
644 .probe = imx_wm8960_probe,
645 .remove = imx_wm8960_remove,
646 };

2、 model 屬性

model 屬性值也是一個字串,一般 model 屬性描述裝置模組資訊,比如名字什麼的,比
如:
model = "wm8960-audio";

3、 status 屬性

status 屬性看名字就知道是和裝置狀態有關的, status 屬性值也是字串,字串是裝置的
狀態資訊,可選的狀態如表 43.3.3.1 所示:
在這裡插入圖片描述

4、 #address-cells 和#size-cells 屬性

這兩個屬性的值都是無符號 32 位整形, #address-cells 和#size-cells 這兩個屬性可以用在任何擁有子節點的裝置中,用於描述子節點的地址資訊。 #address-cells 屬性值決定了子節點 reg 屬性中地址資訊所佔用的字長(32 位), #size-cells 屬性值決定了子節點 reg 屬性中長度資訊所佔的字長(32 位)。 #address-cells 和#size-cells 表明了子節點應該如何編寫 reg 屬性值,一般 reg 屬性都是和地址有關的內容,和地址相關的資訊有兩種:起始地址和地址長度, reg 屬性的格式一為:reg = <address1 length1 address2 length2 address3 length3……>
#address-cells 和 #size-cells

address-cells:指定在 reg 屬性中地址部分使用的單元數(通常是 32 位或 64 位單元)。這個屬性定義了子節點的 reg 屬性中地址欄位的長度。

size-cells:指定在 reg 屬性中大小(length)部分使用的單元數。這個屬性定義了子節點的 reg 屬性中大小欄位的長度。

5、 reg 屬性

reg 屬性前面已經提到過了, reg 屬性的值一般是(address, length)對。 reg 屬性一般用於描述裝置地址空間資源資訊,一般都是某個外設的暫存器地址範圍資訊,比如在 imx6ull.dtsi 中有如下內容:

示例程式碼 43.3.3.3 uart1 節點資訊
323 uart1: serial@02020000 {
324 compatible = "fsl,imx6ul-uart",
325 "fsl,imx6q-uart", "fsl,imx21-uart";
326 reg = <0x02020000 0x4000>;
327 interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
328 clocks = <&clks IMX6UL_CLK_UART1_IPG>,
329 <&clks IMX6UL_CLK_UART1_SERIAL>;
330 clock-names = "ipg", "per";
331 status = "disabled";
332 };

因此 reg 屬性中 address=0x02020000, length=0x4000。查閱《I.MX6ULL 參考手冊》可知, I.MX6ULL 的 UART1 暫存器首地址為 0x02020000,但是 UART1 的地址長度(範圍)並沒有 0x4000 這麼多,這裡我們重點是獲取 UART1 暫存器首地址。

6、 ranges 屬性

ranges屬性值可以為空或者按照(child-bus-address,parent-bus-address,length)格式編寫的數字矩陣, ranges 是一個地址對映/轉換表, ranges 屬性每個專案由子地址、父地址和地址空間長度這三部分組成:

child-bus-address:子匯流排地址空間的實體地址,由父節點的#address-cells 確定此實體地址
所佔用的字長。
parent-bus-address: 父匯流排地址空間的實體地址,同樣由父節點的#address-cells 確定此物
理地址所佔用的字長。
length: 子地址空間的長度,由父節點的#size-cells 確定此地址長度所佔用的字長。
ranges 屬性不為空的示例程式碼如下所示:

示例程式碼 43.3.3.5 ranges 屬性不為空
1 soc {
2 compatible = "simple-bus";
3 #address-cells = <1>;
4 #size-cells = <1>;
5 ranges = <0x0 0xe0000000 0x00100000>;
6 7
serial {
8 device_type = "serial";
9 compatible = "ns16550";
10 reg = <0x4600 0x100>;
11 clock-frequency = <0>;
12 interrupts = <0xA 0x8>;
13 interrupt-parent = <&ipic>;
14 };
15 };

第 5 行,節點 soc 定義的 ranges 屬性,值為<0x0 0xe0000000 0x00100000>,此屬性值指定
了一個 1024KB(0x00100000)的地址範圍,子地址空間的物理起始地址為 0x0,父地址空間的物
理起始地址為 0xe0000000。
第 10 行, serial 是串列埠裝置節點, reg 屬性定義了 serial 裝置暫存器的起始地址為 0x4600,暫存器長度為 0x100。經過地址轉換, serial 裝置可以從 0xe0004600 開始進行讀寫操作,0xe0004600
=0x4600+0xe0000000。具體計算原因如下:
在裝置樹中,ranges 屬性是用來描述如何將子地址空間對映到父地址空間的。這是在匯流排橋接或記憶體對映時非常重要的一個屬性,它確保了裝置地址的正確轉換和訪問。

解析 ranges 屬性

5 ranges = <0x0 0xe0000000 0x00100000>;

這裡的 ranges 屬性包含三個部分:

  1. 子地址空間的起始地址0x0
  2. 父地址空間的起始地址0xe0000000
  3. 對映的大小0x00100000(1024KB)

這意味著在 soc 節點下的所有子裝置的地址(子地址空間),從 0x0 開始,對映到父地址空間的0xe0000000 開始,對映區域大小為 1024KB。

裝置地址轉換

當我們看到 serial 節點的 reg 屬性:

10 reg = <0x4600 0x100>;

這裡定義了:

  • 裝置暫存器的起始地址0x4600(這是在子地址空間中的地址)
  • 暫存器長度0x100

由於 ranges 屬性已經定義了子地址空間到父地址空間的對映,0x4600 這個子地址需要轉換到父地址空間中。根據 ranges 的定義,子地址 0x0 對應父地址 0xe0000000。因此,任何子地址都需要加上 0xe0000000 來轉換為父地址空間中的實際實體地址。

所以,計算 serial 裝置的實體地址如下:

\[\text{實體地址} = \text{子地址} + \text{父地址起始} = 0x4600 + 0xe0000000 = 0xe0004600 \]

這就是為什麼 0xe0004600 的計算方式是 0x4600 + 0xe0000000。這樣的地址轉換確保了即使 serial 裝置在 soc 子地址空間中的地址是 0x4600,在實際的實體記憶體中也能正確地訪問到裝置的暫存器。

7、 name 屬性

name 屬性值為字串, name 屬性用於記錄節點名字, name 屬性已經被棄用,不推薦使用
name 屬性,一些老的裝置樹檔案可能會使用此屬性。

8、 device_type 屬性

device_type 屬性值為字串, IEEE 1275 會用到此屬性,用於描述裝置的 FCode,但是裝置樹沒有 FCode,所以此屬性也被拋棄了。此屬性只能用於 cpu 節點或者 memory 節點。imx6ull.dtsi 的 cpu0 節點用到了此屬性,內容如下所示:

示例程式碼 43.3.3.6 imx6ull.dtsi 檔案程式碼段
54 cpu0: cpu@0 {
55 compatible = "arm,cortex-a7";
56 device_type = "cpu";
57 reg = <0>;
......
89 };

根節點 compatible 屬性

每個節點都有 compatible 屬性,根節點“/”也不例外, imx6ull-alientek-emmc.dts 檔案中根節點的 compatible 屬性內容如下所示:

示例程式碼 43.3.4.1 imx6ull-alientek-emmc.dts 根節點 compatible 屬性
14 / {
15 model = "Freescale i.MX6 ULL 14x14 EVK Board";
16 compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
......
148 }

透過根節點的 compatible 屬性可以知道我們所使用的裝置,一般第一個值描述了所使用的硬體裝置名字,比如這裡使用的是“imx6ull-14x14-evk”這個裝置,第二個值描述了裝置所使用的 SOC,比如這裡使用的“imx6ull”這顆 SOC。 Linux 核心會透過根節點的 compoatible 屬性檢視是否支援此裝置,如果支援的話裝置就會啟動 Linux 核心。接下來我們就來學習一下 Linux 核心在使用裝置樹前後是如何判斷是否支援某款裝置的。

1、使用裝置樹之前裝置匹配方法

在沒有使用裝置樹以前, uboot 會向 Linux 核心傳遞一個叫做 machine id 的值, machine id也就是裝置 ID,告訴 Linux 核心自己是個什麼裝置,看看 Linux 核心是否支援。 Linux 核心是支援很多裝置的,針對每一個裝置(板子), Linux核心都用MACHINE_START和MACHINE_END來定義一個 machine_desc 結構體來描述這個裝置,比如在檔案 arch/arm/mach-imx/machmx35_3ds.c 中有如下定義:

示例程式碼 43.3.4.2 MX35_3DS 裝置
613 MACHINE_START(MX35_3DS, "Freescale MX35PDK")
614 /* Maintainer: Freescale Semiconductor, Inc */
615 .atag_offset = 0x100,
616 .map_io = mx35_map_io,
617 .init_early = imx35_init_early,
618 .init_irq = mx35_init_irq,
619 .init_time = mx35pdk_timer_init,
620 .init_machine = mx35_3ds_init,
621 .reserve = mx35_3ds_reserve,
622 .restart = mxc_restart,
623 MACHINE_END

上述程式碼就是定義了“ Freescale MX35PDK”這個裝置,其中 MACHINE_START 和MACHINE_END 定義在檔案 arch/arm/include/asm/mach/arch.h 中,內容如下:

示例程式碼 43.3.4.3 MACHINE_START 和 MACHINE_END 宏定義
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};

根據 MACHINE_START 和 MACHINE_END 的宏定義,將示例程式碼 43.3.4.2 展開後如下所示:

示例程式碼 43.3.4.3 展開以後
1 static const struct machine_desc __mach_desc_MX35_3DS \
2 __used \
3 __attribute__((__section__(".arch.info.init"))) = {
4 .nr = MACH_TYPE_MX35_3DS,
5 .name = "Freescale MX35PDK",
6 /* Maintainer: Freescale Semiconductor, Inc */
7 .atag_offset = 0x100,
8 .map_io = mx35_map_io,
9 .init_early = imx35_init_early,
10 .init_irq = mx35_init_irq,
11 .init_time = mx35pdk_timer_init,
12 .init_machine = mx35_3ds_init,
13 .reserve = mx35_3ds_reserve,
14 .restart = mxc_restart,
15 };

這裡定義了一個 machine_desc 型別的結構體變數__mach_desc_MX35_3DS , 這 個 變 量 存 儲 在 “.arch.info.init ” 段 中 。 第 4 行 MACH_TYPE_MX35_3DS 就 是 “ Freescale MX35PDK ” 這 個 板 子 的 machine id 。MACH_TYPE_MX35_3DS 定義在檔案 include/generated/mach-types.h 中,此檔案定義了大量的machine id,內容如下所示:

示例程式碼 43.3.4.3 mach-types.h 檔案中的 machine id
15 #define MACH_TYPE_EBSA110 0
16 #define MACH_TYPE_RISCPC 1
17 #define MACH_TYPE_EBSA285 4
18 #define MACH_TYPE_NETWINDER 5
19 #define MACH_TYPE_CATS 6
20 #define MACH_TYPE_SHARK 15
21 #define MACH_TYPE_BRUTUS 16
22 #define MACH_TYPE_PERSONAL_SERVER 17
......
287 #define MACH_TYPE_MX35_3DS 1645
......
1000 #define MACH_TYPE_PFLA03 4575

第 287 行就是 MACH_TYPE_MX35_3DS 的值,為 1645。前面說了, uboot 會給 Linux 核心傳遞 machine id 這個引數, Linux 核心會檢查這個 machineid,其實就是將 machine id 與示例程式碼 43.3.4.3 中的這些 MACH_TYPE_XXX 宏進行對比,看看有沒有相等的,如果相等的話就表示 Linux 核心支援這個裝置,如果不支援的話那麼這個裝置就沒法啟動 Linux 核心。

2、使用裝置樹以後的裝置匹配方法

當 Linux 內 核 引 入 設 備 樹 以 後 就 不 再 使 用 MACHINE_START 了 , 而 是 換 為 了DT_MACHINE
_START。 DT_MACHINE_START 也定義在檔案 arch/arm/include/asm/mach/arch.h裡面,定義如下:

示例程式碼 43.3.4.4 DT_MACHINE_START 宏
#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,

DT_MACHINE_START 和 MACHINE_START 基本相同,只是.nr 的設定不同,在 DT_MACHINE_START 裡面直接將.nr 設定為~0。說明引入裝置樹以後不會再根據 machineid 來檢查 Linux 核心是否支援某個裝置了。
開啟檔案 arch/arm/mach-imx/mach-imx6ul.c,有如下所示內容:

示例程式碼 43.3.4.5 imx6ull 裝置
208 static const char *imx6ul_dt_compat[] __initconst = {
209 "fsl,imx6ul",
210 "fsl,imx6ull",
211 NULL,
212 };
213
214 DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
215 .map_io = imx6ul_map_io,
216 .init_irq = imx6ul_init_irq,
217 .init_machine = imx6ul_init_machine,
218 .init_late = imx6ul_init_late,
219 .dt_compat = imx6ul_dt_compat,
220 MACHINE_END

dt_compat 成員變數,此成員變數儲存著本裝置相容屬性,示例程式碼 43.3.4.5 中設定.dt_compat = imx6ul_dt_compat, imx6ul_dt_compat 表裡面有"fsl,imx6ul"和"fsl,imx6ull"這兩個相容值。只要某個裝置(板子)根節點“ /”的 compatible 屬性值與imx6ul_dt_compat 表中的任何一個值相等,那麼就表示 Linux 核心支援此裝置。 imx6ull-alientekemmc.dts 中根節點的 compatible 屬性值如下:

compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

其中“fsl,imx6ull”與 imx6ul_dt_compat 中的“fsl,imx6ull”匹配,因此 I.MX6U-ALPHA 開發板可以正常啟動 Linux 核心。
接下來我們簡單看一下 Linux 核心是如何根據裝置樹根節點的 compatible 屬性來匹配出對應的machine
_desc, Linux 核心呼叫 start_kernel 函式來啟動核心, start_kernel 函式會呼叫setup_arch 函式來匹配machine_desc, setup_arch 函式定義在檔案 arch/arm/kernel/setup.c 中,函式內容如下(有縮減):
在這裡插入圖片描述

3、向節點追加或修改內容

假設現在有個六軸晶片fxls8471, fxls8471 要接到 I.MX6U-ALPHA 開發板的 I2C1 介面上,那麼相當於需要在 i2c1 這個節點上新增一個 fxls8471 子節點。先看一下 I2C1 介面對應的節點,開啟檔案 imx6ull.dtsi 檔案,找到如下所示內容:

示例程式碼 43.3.5.1 i2c1 節點
937 i2c1: i2c@021a0000 {
938 #address-cells = <1>;
939 #size-cells = <0>;
940 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
941 reg = <0x021a0000 0x4000>;
942 interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
943 clocks = <&clks IMX6UL_CLK_I2C1>;
944 status = "disabled";
945 };

示例程式碼 43.3.5.1 就是 I.MX6ULL 的 I2C1 節點,現在要在 i2c1 節點下建立一個子節點,這個子節點就是 fxls8471,最簡單的方法就是在 i2c1 下直接新增一個名為 fxls8471 的子節點,如下所示:

示例程式碼 43.3.5.2 新增 fxls8471 子節點
937 i2c1: i2c@021a0000 {
938 #address-cells = <1>;
939 #size-cells = <0>;
940 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
941 reg = <0x021a0000 0x4000>;
942 interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
943 clocks = <&clks IMX6UL_CLK_I2C1>;
944 status = "disabled";
945
946 //fxls8471 子節點
947 fxls8471@1e {
948 compatible = "fsl,fxls8471";
949 reg = <0x1e>;
950 };
951 };

第 947~950 行就是新增的 fxls8471 這個晶片對應的子節點。但是這樣會有個問題! i2c1 節點是定義在 imx6ull.dtsi 檔案中的,而 imx6ull.dtsi 是裝置樹標頭檔案,其他所有使用到 I.MX6ULL這顆 SOC 的板子都會引用 imx6ull.dtsi 這個檔案。直接在 i2c1 節點中新增 fxls8471 就相當於在其他的所有板子上都新增了 fxls8471 這個裝置,但是其他的板子並沒有這個裝置。
這裡就要引入另外一個內容,那就是如何向節點追加資料,我們現在要解決的就是如何向i2c1 節點追加一個名為 fxls8471 的子節點,而且不能影響到其他使用到 I.MX6ULL 的板子。I.MX6U-ALPHA 開發板使用的裝置樹檔案為 imx6ull-alientek-emmc.dts,因此我們需要在imx6ull-alientek-emmc.dts 檔案中完成資料追加的內容,方式如下:

示例程式碼 43.3.5.3 節點追加資料方法
1 &i2c1 {
2 /* 要追加或修改的內容 */
3 };

第 1 行, &i2c1 表示要訪問 i2c1 這個 label 所對應的節點,也就是 imx6ull.dtsi 中的“i2c1:i2c@021a0000”。
第 2 行,花括號內就是要向 i2c1 這個節點新增的內容,包括修改某些屬性的值。

建立小型模板裝置樹

我們就以 I.MX6ULL 這個 SOC 為例,我們需要在裝置樹裡面描述的內容如下:
①、 I.MX6ULL 這個 Cortex-A7 架構的 32 位 CPU。
②、 I.MX6ULL 內部 ocram,起始地址 0x00900000,大小為 128KB(0x20000)。
③、 I.MX6ULL 內部 aips1 域下的 ecspi1 外設控制器,暫存器起始地址為 0x02008000,大
小為 0x4000。
④、 I.MX6ULL 內部 aips2 域下的 usbotg1 外設控制器,暫存器起始地址為 0x02184000,大
小為 0x4000。
⑤、 I.MX6ULL 內部 aips3 域下的 rngb 外設控制器,暫存器起始地址為 0x02284000,大小
為 0x4000。
為了簡單起見,我們就在裝置樹裡面就實現這些內容即可,首先,搭建一個僅含有根節點“/”的基礎的框架,新建一個名為 myfirst.dts 檔案,在裡面輸入如下所示內容:

示例程式碼 43.4.1 裝置樹基礎框架
1 / {
2 compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull";
3 }

裝置樹框架很簡單,就一個根節點“/”,根節點裡面只有一個 compatible 屬性。我們就在這個基礎框架上面將上面列出的內容一點點新增進來。

1、新增 cpus 節點

首先新增 CPU 節點, I.MX6ULL 採用 Cortex-A7 架構,而且只有一個 CPU,因此只有一個cpu0 節點,完成以後如下所示:

示例程式碼 43.4.2 新增 CPU0 節點
1 / {
2 compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull";
3
4 cpus {
5 #address-cells = <1>;
6 #size-cells = <0>;
7 8
//CPU0 節點
9 cpu0: cpu@0 {
10 compatible = "arm,cortex-a7";
11 device_type = "cpu";
12 reg = <0>;
13 };
14 };
15 }

第 4~14 行, cpus 節點,此節點用於描述 SOC 內部的所有 CPU,因為 I.MX6ULL 只有一個CPU,因此只有一個 cpu0 子節點。

2、新增 soc 節點

像 uart, iic 控制器等等這些都屬於 SOC 內部外設,因此一般會建立一個叫做 soc 的父節點來管理這些 SOC 內部外設的子節點,新增 soc 節點以後的 myfirst.dts 檔案內容如下所示:

16 //soc 節點
17 soc {
18 #address-cells = <1>;
19 #size-cells = <1>;
20 compatible = "simple-bus";
21 ranges;
}

soc 節點, soc 節點設定#address-cells = <1>, #size-cells = <1>,這樣 soc 子節點的 reg 屬性中起始地佔用一個字長,地址空間長度也佔用一個字長。
第 21 行, ranges 屬性, ranges 屬性為空,說明子空間和父空間地址範圍相同。

3、新增 ocram 節點

根據第②點的要求,新增 ocram 節點, ocram 是 I.MX6ULL 內部 RAM,因此 ocram 節點應該是 soc 節點的子節點。 ocram 起始地址為 0x00900000,大小為 128KB(0x20000),新增 ocram節點以後 myfirst.dts 檔案內容如下所示:

24 ocram: sram@00900000 {
25 compatible = "fsl,lpm-sram";
26 reg = <0x00900000 0x20000>;

ocram 節點,第 24 行節點名字@後面的 0x00900000 就是 ocram 的起始地址。第 26 行的 reg 屬性也指明瞭 ocram 記憶體的起始地址為 0x00900000,大小為 0x20000。

4、新增 aips1、 aips2 和 aips3 這三個子節點

I.MX6ULL 內部分為三個域: aips1~3,這三個域分管不同的外設控制器, aips1~3 這三個域對應的記憶體範圍如表 43.4.1 所示
在這裡插入圖片描述
在這裡插入圖片描述
aips1~3 這三個域都屬於 soc 節點的子節點,完成以後的 myfirst.dts 檔案內容如下所示:

29 //aips1 節點
30 aips1: aips-bus@02000000 {
31 compatible = "fsl,aips-bus", "simple-bus";
32 #address-cells = <1>;
33 #size-cells = <1>;
34 reg = <0x02000000 0x100000>;
35 ranges;
36 }
38 //aips2 節點
39 aips2: aips-bus@02100000 {
40 compatible = "fsl,aips-bus", "simple-bus";
41 #address-cells = <1>;
42 #size-cells = <1>;
43 reg = <0x02100000 0x100000>;
44 ranges;
45 }
46
47 //aips3 節點
48 aips3: aips-bus@02200000 {
49 compatible = "fsl,aips-bus", "simple-bus";
50 #address-cells = <1>;
51 #size-cells = <1>;
52 reg = <0x02200000 0x100000>;
53 ranges;
54 }

5、新增 ecspi1、 usbotg1 和 rngb 這三個外設控制器節點

在 myfirst.dts 檔案中加入 ecspi1, usbotg1 和 rngb 這三個外設控制器對應的節點,其中 ecspi1 屬於 aips1 的子節點, usbotg1 屬於 aips2 的子節點, rngb 屬於 aips3 的子節點。最終的 myfirst.dts 檔案內容如下:

示例程式碼 43.4.6 新增 ecspi1、 usbotg1 和 rngb 這三個節點
1 / {
2 compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull";
3 4
cpus {
5 #address-cells = <1>;
6 #size-cells = <0>;
7 8
//CPU0 節點
9 cpu0: cpu@0 {
10 compatible = "arm,cortex-a7";
11 device_type = "cpu";
12 reg = <0>;
13 };
14 };
15
16 //soc 節點
17 soc {
18 #address-cells = <1>;
19 #size-cells = <1>;
20 compatible = "simple-bus";
21 ranges;
22
23 //ocram 節點
24 ocram: sram@00900000 {
25 compatible = "fsl,lpm-sram";
26 reg = <0x00900000 0x20000>;
27 };
28
29 //aips1 節點
30 aips1: aips-bus@02000000 {
31 compatible = "fsl,aips-bus", "simple-bus";
32 #address-cells = <1>;
33 #size-cells = <1>;
34 reg = <0x02000000 0x100000>;
35 ranges;
36
37 //ecspi1 節點
38 ecspi1: ecspi@02008000 {
39 #address-cells = <1>;
40 #size-cells = <0>;
41 compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
42 reg = <0x02008000 0x4000>;
43 status = "disabled";
44 };
45 }
46
47 //aips2 節點
48 aips2: aips-bus@02100000 {
49 compatible = "fsl,aips-bus", "simple-bus";
50 #address-cells = <1>;
51 #size-cells = <1>;
52 reg = <0x02100000 0x100000>;
53 ranges;
54
55 //usbotg1 節點
56 usbotg1: usb@02184000 {
57 compatible = "fsl,imx6ul-usb", "fsl,imx27-usb";
58 reg = <0x02184000 0x4000>;
59 status = "disabled";
60 };
61 }
62
63 //aips3 節點
64 aips3: aips-bus@02200000 {
65 compatible = "fsl,aips-bus", "simple-bus";
66 #address-cells = <1>;
67 #size-cells = <1>;
68 reg = <0x02200000 0x100000>;
69 ranges;
70
71 //rngb 節點
72 rngb: rngb@02284000 {
73 compatible = "fsl,imx6sl-rng", "fsl,imx-rng", "imxrng";
74 reg = <0x02284000 0x4000>;
75 };
76 }
77 }
78 }

裝置樹在系統中的體現

在這裡插入圖片描述

1、根節點“/”各個屬性

根節點屬性屬性表現為一個個的檔案(圖中細字型檔案),比如上圖中的“#address-cells”、“#size-cells”、“compatible”、“model”和“name”這 5 個檔案,
檔案 model 的內容是“Freescale i.MX6 ULL 14x14 EVK Board”,檔案 compatible 的內容為“fsl,imx6ull-14x14-evkfsl,imx6ull”。開啟檔案 imx6ull-alientek-emmc.dts檢視一下,這不正是根節點“/”的 model 和 compatible 屬性值。
在這裡插入圖片描述
在這裡插入圖片描述

2、根節點“/”各子節點

各個資料夾(圖中粗字型資料夾)就是根節點“/”的各個子節點,比如“aliases”、“ backlight”、“ chosen”和“ clocks”等等。大家可以檢視一下 imx6ull-alientek-emmc.dts 和imx6ull.dtsi 這兩個檔案,看看根節點的子節點都有哪些,看看是否和圖 43.5.1 中的一致。/proc/device-tree 目錄就是裝置樹在根檔案系統中的體現,同樣是按照樹形結構組織的,進入/proc/device-tree/soc 目錄中就可以看到 soc 節點的所有子節點,如圖 43.5.3 所示:
在這裡插入圖片描述

3、特殊節點

在根節點“/”中有兩個特殊的子節點: aliases 和 chosen,我們接下來看一下這兩個特殊的子節點。

1 、aliases 子節點

開啟 imx6ull.dtsi 檔案, aliases 節點內容如下所示:

示例程式碼 43.6.1.1 aliases 子節點
18 aliases {
19 can0 = &flexcan1;
20 can1 = &flexcan2;
21 ethernet0 = &fec1;
22 ethernet1 = &fec2;
23 gpio0 = &gpio1;
24 gpio1 = &gpio2;
......
42 spi0 = &ecspi1;
43 spi1 = &ecspi2;
44 spi2 = &ecspi3;
45 spi3 = &ecspi4;
46 usbphy0 = &usbphy1;
47 usbphy1 = &usbphy2;
48 };

單詞 aliases 的意思是“別名”,因此 aliases 節點的主要功能就是定義別名,定義別名的目的就是為了方便訪問節點。不過我們一般會在節點命名的時候會加上 label,然後透過&label來訪問節點,這樣也很方便,而且裝置樹裡面大量的使用&label 的形式來訪問節點。

2、chosen 子節點

chosen 並不是一個真實的裝置, chosen 節點主要是為了 uboot 向 Linux 核心傳遞資料,重點是 bootargs 引數。一般.dts 檔案中 chosen 節點通常為空或者內容很少, imx6ull-alientekemmc.dts 中 chosen 節點內容如下所示:

示例程式碼 43.6.2.1 chosen 子節點
18 chosen {
19 stdout-path = &uart1;
20 };

chosen 節點僅僅設定了屬性“stdout-path”,表示標準輸出使用 uart1。但是當我們進入到/proc/device
tree/chosen 目錄裡面,會發現多了 bootargs 這個屬性,如圖 43.6.2.1 所示:
在這裡插入圖片描述
bootargs 這個檔案的內容為“console=ttymxc0,115200……”,這個不就是我們在 uboot 中設定的 bootargs 環境變數的值嗎?現在有兩個疑點:
①、我們並沒有在裝置樹中設定 chosen 節點的 bootargs 屬性,那麼圖 43.6.2.1 中 bootargs這個屬性是怎麼產生的?
②、為何 bootargs 檔案的內容和 uboot 中 bootargs 環境變數的值一樣?它們之間有什麼關係?
前面講解 uboot 的時候說過, uboot 在啟動 Linux 核心的時候會將 bootargs 的值傳遞給 Linux核心,bootargs 會作為 Linux 核心的命令列引數, Linux 核心啟動的時候會列印出命令列引數(也就是 uboot 傳遞進來的 bootargs 的值),如圖 43.6.2.3 所示:
在這裡插入圖片描述
chosen 節點的 bootargs 屬性不是我們在裝置樹裡面設定的,那麼只有一種可能,那就是 uboot 自己在chosen 節點裡面新增了 bootargs 屬性!並且設定 bootargs 屬性的值為 bootargs環境變數的值。因為在啟動 Linux 核心之前,只有 uboot 知道 bootargs 環境變數的值,並且 uboot也知道.dtb 裝置樹檔案在 DRAM 中的位置。在common/fdt_support.c 檔案中發現了“chosen”的身影, fdt_support.c 檔案中有個 fdt_chosen 函式,此函式內容如下所示:

示例程式碼 43.6.2.2 uboot 原始碼中的 fdt_chosen 函式
275 int fdt_chosen(void *fdt)
276 {
277 int nodeoffset;
278 int err;
279 char *str; /* used to set string properties */
280
281 err = fdt_check_header(fdt);
282 if (err < 0) {
283 printf("fdt_chosen: %s\n", fdt_strerror(err));
284 return err;
285 }
286
287 /* find or create "/chosen" node. */
288 nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
289 if (nodeoffset < 0)
290 return nodeoffset;
291
292 str = getenv("bootargs");
293 if (str) {
294 err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
295 strlen(str) + 1);
296 if (err < 0) {
297 printf("WARNING: could not set bootargs %s.\n",
298 fdt_strerror(err));
299 return err;
300 }
301 }
302
303 return fdt_fixup_stdout(fdt, nodeoffset);
304 }

第 288 行,呼叫函式 fdt_find_or_add_subnode 從裝置樹(.dtb)中找到 chosen 節點,如果沒有找到的話就會自己建立一個 chosen 節點。
第 292 行,讀取 uboot 中 bootargs 環境變數的內容。
第 294 行,呼叫函式 fdt_setprop 向 chosen 節點新增 bootargs 屬性,並且 bootargs 屬性的值就是環境變數 bootargs 的內容。證據“實錘”了,就是 uboot 中的 fdt_chosen 函式在裝置樹的 chosen 節點中加入了bootargs屬性,並且還設定了 bootargs 屬性值。接下來我們順著 fdt_chosen 函式一點點的抽絲剝繭,看看都有哪些函式呼叫了 fdt_chosen,一直找到最終的源頭。
在這裡插入圖片描述
中框起來的部分就是函式 do_bootm_linux 函式的執行流程,也就是說do_bootm_linux 函式會透過一系列複雜的呼叫,最終透過 fdt_chosen 函式在 chosen 節點中加入了 bootargs 屬性。而我們透過 bootz 命令啟動 Linux 核心的時候會執行 do_bootm_linux 函式,
至此,真相大白,一切事情的源頭都源於如下命令:

bootz 80800000 – 83000000

當我們輸入上述命令並執行以後, do_bootz 函式就會執行,然後一切就按照圖中所示的流程開始執行。

Linux 核心解析 DTB 檔案

在這裡插入圖片描述

繫結資訊文件

裝置樹是用來描述板子上的裝置資訊的,不同的裝置其資訊不同,反映到裝置樹中就是屬性不同。那麼我們在裝置樹中新增一個硬體對應的節點的時候從哪裡查閱相關的說明呢?在Linux 核心原始碼中有詳細的.txt 文件描述瞭如何新增節點,這些.txt 文件叫做繫結文件。

裝置樹常用 OF 操作函式

裝置樹描述了裝置的詳細資訊,這些資訊包括數字型別的、字串型別的、陣列型別的,我們在編寫驅動的時候需要獲取到這些資訊。比如裝置樹使用 reg 屬性描述了某個外設的暫存器地址為 0X02005482,長度為0X
400,我們在編寫驅動的時候需要獲取到 reg 屬性的0X02005482 和 0X400 這兩個值,然後初始化外設。Linux 核心給我們提供了一系列的函式來獲取裝置樹中的節點或者屬性資訊,這一系列的函式都有一個統一的字首“of_”,所以在很多資料裡面也被叫做 OF 函式。這些 OF 函式原型都定義在 include/linux/of.h 檔案中。

查詢節點的 OF 函式

裝置都是以節點的形式“掛”到裝置樹上的,因此要想獲取這個裝置的其他屬性資訊,必須先獲取到這個裝置的節點。 Linux 核心使用 device_node 結構體來描述一個節點,此結構體定義在檔案 include/linux/of.h 中,定義如下:

示例程式碼 43.3.9.1 device_node 節點
49 struct device_node {
50 const char *name; /* 節點名字 */
51 const char *type; /* 裝置型別 */
52 phandle phandle;
53 const char *full_name; /* 節點全名 */
54 struct fwnode_handle fwnode;
55
56 struct property *properties; /* 屬性 */
57 struct property *deadprops; /* removed 屬性 */
58 struct device_node *parent; /* 父節點 */
59 struct device_node *child; /* 子節點 */
60 struct device_node *sibling;
61 struct kobject kobj;
62 unsigned long _flags;
63 void *data;
64 #if defined(CONFIG_SPARC)
65 const char *path_component_name;
66 unsigned int unique_id;
67 struct of_irq_controller *irq_trans;
68 #endif
69 };
1、 of_find_node_by_name 函式

of_find_node_by_name 函式透過節點名字查詢指定的節點,函式原型如下:

struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
函式引數和返回值含義如下:
from:開始查詢的節點,如果為 NULL 表示從根節點開始查詢整個裝置樹。
name:要查詢的節點名字。
返回值: 找到的節點,如果為 NULL 表示查詢失敗。
2、 of_find_node_by_type 函式

of_find_node_by_type 函式透過 device_type 屬性查詢指定的節點,函式原型如下:

struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
函式引數和返回值含義如下:
from:開始查詢的節點,如果為 NULL 表示從根節點開始查詢整個裝置樹。
type:要查詢的節點對應的 type 字串,也就是 device_type 屬性值。
返回值: 找到的節點,如果為 NULL 表示查詢失敗。
3、 of_find_compatible_node 函式

of_find_compatible_node 函式根據 device_type 和 compatible 這兩個屬性查詢指定的節點,函式原型如下:

struct device_node *of_find_compatible_node(struct device_node *from,
const char *type,
const char *compatible)
函式引數和返回值含義如下:
from:開始查詢的節點,如果為 NULL 表示從根節點開始查詢整個裝置樹。
type:要查詢的節點對應的 type 字串,也就是 device_type 屬性值,可以為 NULL,表示
忽略掉 device_type 屬性。
compatible: 要查詢的節點所對應的 compatible 屬性列表。
返回值: 找到的節點,如果為 NULL 表示查詢失敗
4、 of_find_matching_node_and_match 函式

of_find_matching_node_and_match 函式透過 of_device_id 匹配表來查詢指定的節點,函式原
型如下:
struct device_node *of_find_matching_node_and_match(struct device_node *from,
const struct of_device_id *matches,
const struct of_device_id **match)
函式引數和返回值含義如下:
from:開始查詢的節點,如果為 NULL 表示從根節點開始查詢整個裝置樹。
matches: of_device_id 匹配表,也就是在此匹配表裡面查詢節點。
match: 找到的匹配的 of_device_id。
返回值: 找到的節點,如果為 NULL 表示查詢失敗

5、 of_find_node_by_path 函式

of_find_node_by_path 函式透過路徑來查詢指定的節點,函式原型如下:

inline struct device_node *of_find_node_by_path(const char *path)
函式引數和返回值含義如下:
path:帶有全路徑的節點名,可以使用節點的別名,比如“/backlight”就是 backlight 這個節點的全路徑。
返回值: 找到的節點,如果為 NULL 表示查詢失敗

查詢父/子節點的 OF 函式

1、 of_get_parent 函式

of_get_parent 函式用於獲取指定節點的父節點(如果有父節點的話),函式原型如下:

struct device_node *of_get_parent(const struct device_node *node)
函式引數和返回值含義如下:
node:要查詢的父節點的節點。
返回值: 找到的父節點。
2、 of_get_next_child 函式

of_get_next_child 函式用迭代的方式查詢子節點,函式原型如下:

struct device_node *of_get_next_child(const struct device_node *node,struct device_node *prev)
函式引數和返回值含義如下:
node:父節點。
prev:前一個子節點,也就是從哪一個子節點開始迭代的查詢下一個子節點。可以設定為
NULL,表示從第一個子節點開始。
返回值: 找到的下一個子節點。

提取屬性值的 OF 函式

節點的屬性資訊裡面儲存了驅動所需要的內容,因此對於屬性值的提取非常重要, Linux 核心中使用結構體 property 表示屬性,此結構體同樣定義在檔案 include/linux/of.h 中,內容如下:

示例程式碼 43.9.3.1 property 結構體
35 struct property {
36 char *name; /* 屬性名字 */
37 int length; /* 屬性長度 */
38 void *value; /* 屬性值 */
39 struct property *next; /* 下一個屬性 */
40 unsigned long _flags;
41 unsigned int unique_id;
42 struct bin_attribute attr;
43 };
1、 of_find_property 函式

of_find_property 函式用於查詢指定的屬性,函式原型如下:

property *of_find_property(const struct device_node *np,const char *name,int *lenp)
函式引數和返回值含義如下:
np:裝置節點。
name: 屬性名字。
lenp:屬性值的位元組數
返回值: 找到的屬性。
2、 of_property_count_elems_of_size 函式

of_property_count_elems_of_size 函式用於獲取屬性中元素的數量,比如 reg 屬性值是一個陣列,那麼使用此函式可以獲取到這個陣列的大小,此函式原型如下:

int of_property_count_elems_of_size(const struct device_node *np,const char *propname,int elem_size)
函式引數和返回值含義如下:
np:裝置節點。
proname: 需要統計元素數量的屬性名字。
elem_size:元素長度。
返回值: 得到的屬性元素數量。
3、 of_property_read_u32_index 函式

of_property_read_u32_index 函式用於從屬性中獲取指定標號的 u32 型別資料值(無符號 32位),比如某個屬性有多個 u32 型別的值,那麼就可以使用此函式來獲取指定標號的資料值,此函式原型如下:

int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index,u32 *out_value)
函式引數和返回值含義如下:
np:裝置節點。
proname: 要讀取的屬性名字。
index:要讀取的值標號。
out_value:讀取到的值
返回值: 0 讀取成功,負值,讀取失敗, -EINVAL 表示屬性不存在, -ENODATA 表示沒有
要讀取的資料, -EOVERFLOW 表示屬性值列表太小。
4、 of_property_read_u8_array 函式of_property_read_u16_array 函式of_property_read_u32_array 函式of_property_read_u64_array 函式

這 4 個函式分別是讀取屬性中 u8、 u16、 u32 和 u64 型別的陣列資料,比如大多數的 reg 屬性都是陣列資料,可以使用這 4 個函式一次讀取出 reg 屬性中的所有資料。這四個函式的原型如下:

int of_property_read_u8_array(const struct device_node *np,
const char *propname,
u8 *out_values,
size_t sz)
int of_property_read_u16_array(const struct device_node *np,
const char *propname,
u16 *out_values,
size_t sz)
int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz)
int of_property_read_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values,size_t sz)
函式引數和返回值含義如下:
np:裝置節點。
proname: 要讀取的屬性名字。
out_value:讀取到的陣列值,分別為 u8、 u16、 u32 和 u64。
sz: 要讀取的陣列元素數量。
返回值: 0,讀取成功,負值,讀取失敗, -EINVAL 表示屬性不存在, -ENODATA 表示沒
有要讀取的資料, -EOVERFLOW 表示屬性值列表太小。
5、 of_property_read_u8 函式of_property_read_u16 函式of_property_read_u32 函式of_property_read_u64 函式

有些屬性只有一個整形值,這四個函式就是用於讀取這種只有一個整形值的屬性,分別用於讀取 u8、 u16、 u32 和 u64 型別屬性值,函式原型如下:

int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)
int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)
int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)
int of_property_read_u64(const struct device_node *np,const char *propname,u64 *out_value)
函式引數和返回值含義如下:
np:裝置節點。
proname: 要讀取的屬性名字。
out_value:讀取到的陣列值。
返回值: 0,讀取成功,負值,讀取失敗, -EINVAL 表示屬性不存在, -ENODATA 表示沒
有要讀取的資料, -EOVERFLOW 表示屬性值列表太小。
6、 of_property_read_string 函式

of_property_read_string 函式用於讀取屬性中字串值,函式原型如下:

int of_property_read_string(struct device_node *np,const char *propname,const char **out_string)
函式引數和返回值含義如下:
np:裝置節點。
proname: 要讀取的屬性名字。
out_string:讀取到的字串值。
返回值: 0,讀取成功,負值,讀取失敗。
7、 of_n_addr_cells 函式

of_n_addr_cells 函式用於獲取#address-cells 屬性值,函式原型如下:

int of_n_addr_cells(struct device_node *np)
函式引數和返回值含義如下:
np:裝置節點。
返回值: 獲取到的#address-cells 屬性值。
8、 of_n_size_cells 函式

of_size_cells 函式用於獲取#size-cells 屬性值,函式原型如下:

int of_n_size_cells(struct device_node *np)
函式引數和返回值含義如下:
np:裝置節點。
返回值: 獲取到的#size-cells 屬性值。

其他常用的 OF 函式

1、 of_device_is_compatible 函式

of_device_is_compatible 函式用於檢視節點的 compatible 屬性是否有包含 compat 指定的字串,也就是檢查裝置節點的相容性,函式原型如下:

int of_device_is_compatible(const struct device_node *device,const char *compat)
函式引數和返回值含義如下:
device:裝置節點。
compat:要檢視的字串。
返回值: 0,節點的 compatible 屬性中不包含 compat 指定的字串; 正數,節點的 compatible
屬性中包含 compat 指定的字串。
2、 of_get_address 函式

of_get_address 函式用於獲取地址相關屬性,主要是“reg”或者“assigned-addresses”屬性
值,函式原型如下:
const __be32 *of_get_address(struct device_node *dev,
int index,
u64 *size,
unsigned int *flags)
函式引數和返回值含義如下:
dev:裝置節點。
index:要讀取的地址標號。
size:地址長度。
flags:引數,比如 IORESOURCE_IO、 IORESOURCE_MEM 等
返回值: 讀取到的地址資料首地址,為 NULL 的話表示讀取失敗。

3、 of_translate_address 函式

of_translate_address 函式負責將從裝置樹讀取到的地址轉換為實體地址,函式原型如下:

u64 of_translate_address(struct device_node *dev,const __be32 *in_addr)
函式引數和返回值含義如下:
dev:裝置節點。
in_addr:要轉換的地址。
返回值: 得到的實體地址,如果為 OF_BAD_ADDR 的話表示轉換失敗。
4、 of_address_to_resource 函式

IIC、 SPI、 GPIO 等這些外設都有對應的暫存器,這些暫存器其實就是一組記憶體空間, Linux核心使用 resource 結構體來描述一段記憶體空間,“resource”翻譯出來就是“資源”,因此用 resource結構體描述的都是裝置資源資訊, resource 結構體定義在檔案 include/linux/ioport.h 中,定義如下:

示例程式碼 43.9.4.1 resource 結構體
18 struct resource {
19 resource_size_t start;
20 resource_size_t end;
21 const char *name;
22 unsigned long flags;
23 struct resource *parent, *sibling, *child;
24 };

對於 32 位的 SOC 來說, resource_size_t 是 u32 型別的。其中 start 表示開始地址, end 表示結束地址,name 是這個資源的名字, flags 是資源標誌位,一般表示資源型別,可選的資源標誌定義在檔案include/linux/ioport.h 中,如下所示:

示例程式碼 43.9.4.2 資源標誌
1 #define IORESOURCE_BITS 0x000000ff
2 #define IORESOURCE_TYPE_BITS 0x00001f00
3 #define IORESOURCE_IO 0x00000100
4 #define IORESOURCE_MEM 0x00000200
5 #define IORESOURCE_REG 0x00000300
6 #define IORESOURCE_IRQ 0x00000400
7 #define IORESOURCE_DMA 0x00000800
8 #define IORESOURCE_BUS 0x00001000
9 #define IORESOURCE_PREFETCH 0x00002000
10 #define IORESOURCE_READONLY 0x00004000
11 #define IORESOURCE_CACHEABLE 0x00008000
12 #define IORESOURCE_RANGELENGTH 0x00010000
13 #define IORESOURCE_SHADOWABLE 0x00020000
14 #define IORESOURCE_SIZEALIGN 0x00040000
15 #define IORESOURCE_STARTALIGN 0x00080000
16 #define IORESOURCE_MEM_64 0x00100000
17 #define IORESOURCE_WINDOW 0x00200000
18 #define IORESOURCE_MUXED 0x00400000
19 #define IORESOURCE_EXCLUSIVE 0x08000000
20 #define IORESOURCE_DISABLED 0x10000000
21 #define IORESOURCE_UNSET 0x20000000
22 #define IORESOURCE_AUTO 0x40000000
23 #define IORESOURCE_BUSY 0x80000000

大 家 一 般 最 常 見 的 資 源 標 志 就 是 IORESOURCE_MEM 、 IORESOURCE_REG 和IORESOURCE_IRQ 等。接下來我們回到 of_address_to_resource 函式,此函式看名字像是從裝置樹裡面提取資源值,但是本質上就是將 reg 屬性值,然後將其轉換為 resource 結構體型別,函式原型如下所示

int of_address_to_resource(struct device_node *dev,
int index,
struct resource *r)
函式引數和返回值含義如下:
dev:裝置節點。
index:地址資源標號。
r:得到的 resource 型別的資源值。
返回值: 0,成功;負值,失敗。
5、 of_iomap 函式

of_iomap 函式用於直接記憶體對映,以前我們會透過 ioremap 函式來完成實體地址到虛擬地址的對映,採用裝置樹以後就可以直接透過 of_iomap 函式來獲取記憶體地址所對應的虛擬地址,不需要使用 ioremap 函式了。當然了,你也可以使用 ioremap 函式來完成實體地址到虛擬地址的記憶體對映,只是在採用裝置樹以後,大部分的驅動都使用 of_iomap 函式了。 of_iomap 函式本質上也是將 reg 屬性中地址資訊轉換為虛擬地址,如果 reg 屬性有多段的話,可以透過 index 引數指定要完成記憶體對映的是哪一段, of_iomap 函式原型如下:

void __iomem *of_iomap(struct device_node *np,int index)
函式引數和返回值含義如下:
np:裝置節點。
index: reg 屬性中要完成記憶體對映的段,如果 reg 屬性只有一段的話 index 就設定為 0。
返回值: 經過記憶體對映後的虛擬記憶體首地址,如果為 NULL 的話表示記憶體對映失敗。

相關文章