前言
- 參考
8. Linux裝置樹
Linux3.x 以後引入了裝置樹,用於描述一個硬體平臺的板級細節。
8.1 裝置樹簡介
裝置樹可以被 bootloader(uboot)傳遞到核心,核心從中獲取裝置樹中的硬體資訊。
裝置樹的兩個特點:
- 一:以 樹狀結構 描述硬體資源。
- 二:裝置樹可以像標頭檔案使用,一個裝置樹檔案引用另外一個裝置樹檔案。
幾個常用的縮寫:
- DTS:是指 .dts 格式的檔案,是一種 ASII 文字格式的裝置樹描述,也是我們要編寫的裝置樹原始碼,一般一個 .dts 檔案對應一個硬體平臺,位於 Linux 原始碼的 /arch/arm/boot/dts 目錄下。
- DTC:是指編譯裝置樹原始碼的工具,一般情況下,需要手動安裝這個編譯工具。
- DTB:是裝置樹原始碼編譯生成的檔案。
- .dts:裝置樹原始檔。
- .dtsi:裝置樹標頭檔案。
- .dtb:裝置樹可執行檔案。
8.2 裝置樹框架
裝置樹是由 一個根節點 和 多個子節點 組成。
8.2.1 裝置樹格式
8.2.1.1 DTS 檔案佈局
/dts-v1/; // 表示版本
[memory reservations] // 格式為: /memreserve/ <address> <length>;
/ {
[property definitions]
[child nodes]
};
8.2.1.2 node 格式
node為裝置樹中的基本單元。格式為:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
- label:是節點標籤。可以省略,方便地引用 node。通常,節點標籤通常為基點名稱的縮寫,一般用於追加內容時使用。
- node-name:節點名稱。長度為1-31個字元。可由
0-9 a-z A-Z , . _ + -
組成,且開頭只能是大小寫字母。- 注:根節點沒有節點名,使用
/
來表示。
- 注:根節點沒有節點名,使用
- @unit-address:是單元地址。
@
為分隔符。- 注:節點中 reg 屬性的第一個地址要和這個 單元地址 一致。
- 注:如果節點中沒有 reg 屬性值,則可以省略該 單元地址,但此時必須保證同級別的子節點節點名唯一。反之,若同級別的子節點節點名相同,則單元地址要求不一樣。就是說
node-name@unit-address
整體同級唯一
8.2.1.3 properties 格式
就是 naem = value。
- 格式1:(沒有值)
[label:] property-name;
- 格式2:(支援三種取值)
- arrays of cell:一個或多個 32 位資料,64 位資料使用 2 個 32 位資料表示。
- string:字串。
- bytestring:一個或多個字串。
[label:] property-name = value;
- 例子1:64bit 用兩個 cell 表示,使用 尖括號。
clock-frequency = <0x00000001 0x00000000>;
- 例子2:字串,用 雙引號。
compatible = "lzm-bus";
- 例子3:位元組序列,用 中括號。
local-mac-address = [00 00 12 34 56 78]; // 每個 byte 使用 2 個 16 進位制數來表示
local-mac-address = [000012345678]; // 每個 byte 使用 2 個 16 進位制數來表示
- 例子4:各種組合,用 逗號 隔開。
example = <0x84218421 23>, "hello world";
8.2.1.4 包含 dtsi
一般裝置樹都不需要從零開始寫,只需要包含晶片廠商提供的裝置樹模板,然後再新增,修改即可。
dts 可以包含 dtsi 檔案,也可以包含 .h 檔案。.h 檔案可以定義一些巨集。
/dts-v1/;
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
/ {
……
};
8.2.2 修改、追加裝置樹節點
修改、追加裝置樹節點都可在檔案末尾或新檔案修改或追加。
而修改節點,可參考以下兩種方法:標籤法 或 全路徑法:
標籤法:
// 方法一:在根節點之外使用標籤引用節點
&red_led
{
status = "okay";
}
// 方法二:使用全路徑引用節點
&{/led@0x020C406C}
{
status = "okay";
}
全路徑法:
- 追加節點,類似新建一個簡易的裝置樹一樣。包含根節點到需要新建節點的全路徑。
8.2.3 常用屬性
在節點 {} 中包含的內容時節點屬性。這些屬性資訊就是板級硬體描述的資訊,驅動會通過核心提供的 API 去獲取這些資源資訊。
注意:節點屬性可分為 標準屬性 和 自定義屬性,即是可以自行新增屬性。
8.2.3.1 常用標準屬性
compatible 屬性:
- 屬性值型別:字串。雙引號。
- compatible 表示相容。
- 每一個代表裝置的節點都必須有一個 compatible 屬性值。
- 由一個或多個字串組成,使用 "," 分隔開即可。
- 如:
compatible = "A", "B", "C";
。 - 核心啟動時,會按順序 A、B、C 找到對應的驅動程式,與驅動中 of_match_table 中的值進行匹配,然後載入對應的驅動。
- compatible 是查詢節點的方法之一,還可以通過 節點名 或 節點路徑 找到指定節點。
- compatible 建議格式:"manufacturer,model" ,即是 "廠家名,模組名"。
model 屬性:
- 屬性值型別:字串。雙引號。
- model 定義硬體是什麼。
- 推薦指定裝置的製造商和型號,推薦格式 "製造商,型號"。
- 如:
model = "lzm com,IMX6U-V1";
status 屬性:
- 屬性值型別:字串。雙引號。
- status 表示當前裝置節點狀態,用於禁止和啟動裝置。
- 有如下值可選:
value | description |
---|---|
okay | 裝置正常 |
disabled | 裝置不可操作,但後面可恢復正常 |
fail | 發生嚴重錯誤,需要修復 |
fail-sss | 發生嚴重錯誤,需要修復。sss 表示錯誤資訊 |
#address-cells、#size-cells 屬性:
- 屬性值型別:u32。尖括號。
- #address-cells、#size-cells 是同時出現的。
- #address-cells:表示 address 要用多少個 32 位數來表示。
- #size-cells:表示 size 要用多少個 32 位數來表示。
- 用於設定子節點 reg、ranges 等地址相關屬性的書寫格式。
reg 屬性:
- 屬性值型別:地址、長度資料對。尖括號。
- reg 就是 register,用於表示暫存器地址。
- 用於描述一段記憶體空間。
- reg 屬性的值是一些列的 。
- 用多少個 32 位的數來表示是由其父節點的 #address-cells、#size-cells 決定的。
- 如:
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
ranges 屬性:
- 屬性值型別:任意數量的 <子地址、父地址、地址長度>編碼。尖括號。
- 該屬性提供了子節點地址空間和父地址空間的對映(轉換)方法。
- 如:
ranges=<0x05 0x10 0x20>
name、ldevice_type 屬性:
- 屬性值型別:字串。雙引號。
- 過時,不建議使用。
8.2.3.2 自定義屬性
名稱及內容可自定義,但是名稱不能與標準屬性重名。獲取方式,後述。
8.2.4 常用節點
根節點:
- dts 檔案中必須有一個 根節點。
- 根節點 必須有以下屬性:
- #address-cells。
- #size-cells。
- compatible:定義一些列的字串,用於指定核心中哪個 machine_desc 可以支援本裝置。即是相容性。
- model:表示本硬體型號。
CPU:
- 一般都在 dtsi 檔案中定義好了,不需要我們設定。
memory:
- 這個是表示板子記憶體大小,一般由開發板開發者定義的。
chosen:
- 該節點主要作用於向核心傳遞引數。如:
chosen
{
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};
aliases:
- aliases 是為了給其它節點起個別名。如:
aliases {
can0 = &flexcan1;
gpio0 = &gpio1;
}
- can0 就是 flexcan1 的別名。
8.3 編譯、更換裝置樹
一般的程式猿會修改裝置樹即可,不必從零開始。
8.3.1 在核心中編譯裝置樹(推薦)
編譯時需要設定一下三個環境變數 ARCH、CROSS_COMPILE、PATH。
在開發環境中進入板子對應的核心原始碼目錄,使用核心中的 makefile 即可,執行如下命令來編譯 dtb 檔案:
make dtbs V=1
上述命令是單獨編譯裝置樹。
會編譯以下裝置樹:
在arch/arm/Makefile 或 arch/arm/boot/Makefile 或 arch/arm/boot/dts/Makefile
等相關 Makefile 中找到 dtb-$(xxx)
,該值包含的就是要編譯的 dtb 。
如該檔案中巨集 dtb-$(CONFIG_SOC_XXX) 包含的 .dtb 就會被編譯出來。
如果想編譯自己的裝置樹,新增該值內容,並把自己的裝置樹放在 arch/arm/boot/dts
下即可。
(具體檢視該 arch/arm/boot/Makefile 內容)
8.3.2 人工編譯(不推薦)
意思是手工使用 dtc 工具直接編譯。
dtc 工具存放於核心目錄 scripts/dtc 下。
若直接使用 dtc 工具手工編譯的話,包含其它檔案時不能使用 #include
,而必須使用 /include
。
- 因為核心中 make dtb 時能使用
#include
是因為使用了 交叉編譯鏈。
編譯、反編譯的示例命令如下,-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
8.3.3 更換裝置樹
一般步驟:
- 確保好三個環境變數。
- 在核心原始碼目錄中執行
make dtbs
。 - 生成的裝置樹檔案一般儲存在核心目錄 arch/arm/boot/dts/ 下。
- 把生成的裝置樹檔案替換到板子上。開發板使用的裝置樹一般放在 /boot/ 目錄下。
- 若需要自定義新的裝置樹檔名稱,則修改 /boot/ 目錄下的 uEnv.txt 檔案內容。
8.3.4 檢視裝置樹
目錄 /sys/firmware/devicetree 下以目錄結構呈現的 dtb 檔案。
- 根節點對應 base 目錄。
- 每一個節點對應一個目錄。
- 每一個屬性對應一個檔案。
- 若屬性值為字串,則可以使用 cat 命令列印出來。
- 若屬性值為數值,則可以使用 hexdump 命令列印出來。
目錄 /sys/firmware/fdt 檔案,就是 dtb 格式的裝置樹檔案。
- 可以將其賦值出來,反編譯。
8.4 核心處理裝置樹
8.4.1 裝置樹過程
裝置樹生命過程:
DTS --(PC)--> DTB --(核心)--> device_node -·(核心)·-> platform_device。
流程:
- dts 在 PC 機上被編譯為 dtb 檔案。
- u-boot 把 dtb 檔案傳給核心。
- 核心解析 dtb 檔案,把每一個節點都轉換為 device_node 結構體。
- 對於某些 device_node 結構體,會被轉換為 platform_device 結構體。
對於 device_node 和 platform_device,建議去核心原始碼看看它們的成員。
8.4.2 轉換為 platform_device 的條件
- 根節點下有 compatile 屬性的子節點。
- 含有特定 compatile 屬性的節點的子節點。
- 如果一個節點的 compatile 屬性是以下 4 個值之一,那麼該節點含有 compatile 屬性的 子節點也可以轉換為 platform_device。
simple-bus
;simple-mfd
;isa
;arm,amba-bus
。
- 如果一個節點的 compatile 屬性是以下 4 個值之一,那麼該節點含有 compatile 屬性的 子節點也可以轉換為 platform_device。
- 匯流排 I2C、SPI 節點下的子節點 不轉換 為 platform_device。
- 某個匯流排下的子節點,不應該被轉換為 platform_device。而應該交給對應的匯流排驅動來處理。
8.5 獲取節點函式
在驅動程式中,核心載入裝置樹後。可以通過以下函式獲取到裝置樹節點中的資源資訊。
獲取節點函式及獲取節點內容函式稱為 of 函式。
8.5.1 重要結構體內容
8.5.1.1 device_node
device_node 結構體如下:
struct device_node
{
const char *name;
const char *type;
phandle phandle;
const char *full_name;
struct fwnode_handle fwnode;
struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
- name:節點中的 name 屬性值。
- type:節點中的 device_type 屬性值。
- full_name:節點的名字。
- properties:連結串列,連線該節點的所有屬性。
- parent:指向父節點。
- child:指向子節點。
- sibling:指向兄弟節點。
8.5.1.2 of_device_id
of_device_id 結構體如下:
/* Struct used for matching a device */
struct of_device_id
{
char name[32];
char type[32];
char compatible[128];
const void *data;
};
- name:節點中屬性為 name 的值。
- type:節點中屬性為 device_type 的值。
- compatible:節點的名字,在 device_node 結構體後面放一個字串,full_name 指向它。
- data:連結串列,連線該節點的所有屬性。
8.5.2 據節點路徑尋找節點
of_find_node_by_path():
- 函式原型:
struct device_node *of_find_node_by_path(const char *path)
。 - 原始碼路徑:核心原始碼/include/linux/of.h。
- path:節點在裝置樹中的路徑。
- 返回值:
- 成功:返回 device_node 結構體指標。
- 失敗:NULL。
8.5.3 據節點型別尋找節點
of_find_node_by_type():
- 函式原型:
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
。 - 原始碼路徑:核心原始碼/include/linux/of.h。
- from:指定從哪裡開始找(不包含本身),若要從根節點開始找,且包含根節點,則該值未 NULL。
- type: 要查詢節點的型別,這個型別就是 device_node->type。
- 返回值:
- 成功:返回 device_node 結構體指標。
- 失敗:NULL。
8.5.4 據節點型別和compatible屬性尋找節點
of_find_compatible_node():
- 函式原型:
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
。 - 原始碼路徑:核心原始碼/include/linux/of.h。
- from:指定從哪裡開始找(不包含本身),若要從根節點開始找,且包含根節點,則該值未 NULL。
- type: 要查詢節點的型別,這個型別就是 device_node->type。
- compatible:需要查詢的節點的 compatible 屬性。
- 返回值:
- 成功:返回 device_node 結構體指標。
- 失敗:NULL。
8.5.5 據匹配表尋找節點
of_find_matching_node_and_match():
- 函式原型:
struct inline device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)
。 - 原始碼路徑:核心原始碼/include/linux/of.h。
- from:指定從哪裡開始找(不包含本身),若要從根節點開始找,且包含根節點,則該值未 NULL。
- matches:of_device_id 匹配表,也就是在此匹配表裡面查詢節點。
- match:找到的匹配的 of_device_id。
- 返回值:
- 成功:返回 device_node 結構體指標。
- 失敗:NULL。
8.5.6 尋找父節點
of_get_parent():
- 函式原型:
struct device_node *of_get_parent(const struct device_node *node)
。 - 原始碼路徑:核心原始碼/include/linux/of.h。
- node:需要查詢要查詢父節點的節點。
- 返回值:
- 成功:返回 device_node 結構體指標。
- 失敗:NULL。
8.5.7 尋找子節點
of_get_child():
- 函式原型:
struct device_node *of_get_child(const struct device_node *node, struct device_node *prev)
。 - 原始碼路徑:核心原始碼/include/linux/of.h。
- node:需要查詢要查詢子節點的節點。
- prev:需要尋找的節點的前一個節點,即是本函式需要尋找 prev 節點的後一個節點。
- 返回值:
- 成功:返回 device_node 結構體指標。
- 失敗:NULL。
8.6 提取節點中的屬性值
8.6.1 重要結構體內容
8.6.1.1 property 結構體
property:
struct property
{
char *name;
int length;
void *value;
struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
};
- name:屬性名。
- lenght:屬性長度。
- value:屬性值。
- next:下一個屬性。
8.6.1.2 resource 結構體
resource 結構體:
struct resource
{
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
8.6.2 查詢節點屬性值
of_find_property():
-
函式原型:
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
-
原始碼路徑:核心原始碼/include/linux/of.h。
-
np:裝置節點。
-
name:屬性名稱。
-
lenp:實際獲得屬性值的長度(函式輸出引數)。
-
返回值:
- 成功:返回 property 結構體,獲取得到的屬性。
- 失敗:返回 NULL。
-
可以瞭解下 獲取屬性值函式 of_get_property() ,與 of_find_property() 的區別是一個返回屬性值,一個返回屬性結構體。
8.6.3 獲取整型屬性
of_property_read_u8_array:
- 以下函式分別讀取 8、16、32、64 位資料:
//8位整數讀取函式
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
//16位整數讀取函式
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)
//32位整數讀取函式
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)
//64位整數讀取函式
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
- 原始碼路徑:核心原始碼/include/linux/of.h。
- np:指定裝置節點。
- propname:哪個屬性。
- out_values:儲存讀取到的資料(函式輸出引數)。
- sz:設定讀取的長度。
- 返回值:
- 成功:0.
- 失敗:非零值
- -EINVAL:屬性不存在。
- -ENODATA:沒有要讀取的資料。
- -EOVERFLOW:屬性值列表太小。
8.6.4 簡化後的讀取整型屬性函式
of_property_read_u8:
- 其讀取長度為 1。
//8位整數讀取函式
int of_property_read_u8 (const struct device_node *np, const char *propname,u8 *out_values)
//16位整數讀取函式
int of_property_read_u16 (const struct device_node *np, const char *propname,u16 *out_values)
//32位整數讀取函式
int of_property_read_u32 (const struct device_node *np, const char *propname,u32 *out_values)
//64位整數讀取函式
int of_property_read_u64 (const struct device_node *np, const char *propname,u64 *out_values)
8.6.5 讀取字串屬性
of_property_read_string_index:(推薦)
- 函式原型:
int of_property_read_string_index(const struct device_node *np,const char *propname, int index, const char **out_string)
- 原始碼路徑:核心原始碼/include/linux/of.h。
- np:指定裝置節點。
- propname:哪個屬性。
- index:指定要讀取該屬性值得第幾個字串。index 從 0 開始。
- out_string:獲取到的字串的指標(函式輸出引數)。
- 返回:
- 成功:0;
- 失敗:失敗錯誤碼。
of_property_read_string:(不推薦)
- 函式原型:
int of_property_read_string(const struct device_node *np,const char *propname,const char **out_string)
- 原始碼路徑:核心原始碼/include/linux/of.h。
- 引數同上。
8.6.6 讀取 bool 型屬性函式
of_property_read_bool():
- 函式原型:
static inline bool of_property_read_bool(const struct device_node *np, const char *propname)
- np:裝置節點。
- propname:屬性名稱。
- 返回值:只返回該屬性存不存在。
- 若要讀取該屬性值,需要用到函式 of_find_property。
8.6.7 記憶體對映相關 of 函式
裝置樹提供暫存器的地址段,但是一般情況下都會使用 ioremap 對映為虛擬地址使用。
of_address_to_resource 只是獲取 reg 的值,也就是暫存器值。
of_iomap 函式就是獲取 reg 屬性值&指定哪一段記憶體&對映為虛擬地址。
of_address_to_resource:
- 函式原型:
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
- 原始碼路徑:
核心原始碼/drivers/of/address.c
。 - np:裝置節點。
- index:指定對映那一段記憶體。通常情況下,reg 屬性包含多段。標號從 0 開始。
- r:resource 結構體,得到的地址資訊(函式輸出引數)。
- 返回:
- 成功:0;
- 失敗:失敗錯誤碼。
of_iomap:
- 函式原型:
void __iomem *of_iomap(struct device_node *np, int index)
- 原始碼路徑:核心原始碼/include/linux/of.h。
- np:裝置節點。
- index:指定對映那一段記憶體。通常情況下,reg 屬性包含多段。標號從 0 開始。
- 返回:
- 成功:轉換後的地址。
- 失敗:NULL。