整理了一份 Linux 設備樹基礎知識,建議收藏!

在 platform_device 部分有簡單說明描述設備有兩種方法:一種是使用 platform_device 結構體來指定;另一種是使用設備樹來描述。

本篇筆記我們就來簡單地學習一下設備樹的一些知識。

什麼是設備樹

設備樹簡單理解就是描述設備信息(資源)的一棵樹。設備樹(Device Tree)用代碼體現如下:

這些代碼被保存在. dts/dtsi 後綴文件中,也即設備樹源文件 DTS(DeviceTree Source)。

這些源文件同我們的 C 代碼一樣,並不能直接使用的,而是得經過一個編譯過程生成機器可運行的二進制文件,如:

dts 文件使用 dtc 工具編譯生成 dtb 文件,這個 dtb 文件就是內核可以使用的文件。例如我們的板子跑起來之後,我們系統使用的設備樹文件就存在目錄 / boot 下:

Linux 爲什麼會引入設備樹?

在上一個實驗:【Linux 筆記】LED 驅動實驗(總線設備驅動模型)中我們使用了 platform_device 結構體來描述 led 設備(硬件資源)。既然已經有了描述設備的方法了,爲什麼還要引入設備樹呢?

因爲 Linux 內核中有很多 BSP(板級支持包),不同的 BSP 會包含着不同的描述設備的代碼(.c 或. h 文件)。

隨着芯片的發展,Linux 內核中就包含着越來越多這些描述設備的代碼,導致 Linux 內核代碼會很臃腫。

這導致 Linux 之父 Linus 大發雷霆:"this whole ARM thing is a f*cking pain in the ass"。

因此引入了設備樹文件,從而可精簡一些臃腫的 C 代碼。除此之外,.dts 編譯生成. dtb 文件的過程要比. c 編譯生成驅動模塊、加載驅動模塊的過程要簡單很多,也更方便我們進行開發。

設備樹的語法

設備樹源文件也是需要根據一定規則來編寫的,同 C 語言一樣,也要遵循一些語法規則。下面簡單看一下設備樹的源碼結構及語法。

先看一個設備樹示例:

1、節點格式

label: node-name@unit-address

其中:

label:標號

node-name:節點名字

unit-address:單元地址

label 是標號,可以省略。label 的作用是爲了方便地引用 node。比如:

可以使用下面 2 種方法來修改 uart@fe001000 這個 node:

2、屬性格式

簡單地說, properties 就是 “name=value”, value 有多種取值方式。示例:

interrupts = <17 0xc>;
clock-frequency = <0x00000001 0x00000000>;
compatible = "simple-bus";
local-mac-address = [00 00 12 34 56 78]; // 每個byte使用2個16進制數來表示   
local-mac-address = [000012345678];      // 每個byte使用2個16進制數來表示
compatible = "ns16550""ns8250";   
example = <0xf00f0000 19>, "a strange property format";

3、一些標準屬性

(1) compatible 屬性

“compatible” 表示 “兼容”,對於某個 LED,內核中可能有 A、B、C 三個驅動都支持它,那可以這樣寫:

led {   
    compatible = “A”, “B”, “C”;   
};

內核啓動時,就會爲這個 LED 按這樣的優先順序爲它找到驅動程序:A、B、C。

(2)model 屬性

model 屬性與 compatible 屬性有些類似,但是有差別。compatible 屬性是一個字符串列表,表示可以你的硬件兼容 A、B、C 等驅動;model 用來準確地定義這個硬件是什麼。

比如根節點中可以這樣寫:

{   
    compatible = "samsung,smdk2440""samsung,mini2440";   
    model = "jz2440_v3";   
};

它表示這個單板,可以兼容內核中的 “smdk2440”,也兼容 “mini2440”。

從 compatible 屬性中可以知道它兼容哪些板,但是它到底是什麼板?用 model 屬性來明確。

(3)status 屬性

status 屬性看名字就知道是和設備狀態有關的, status 屬性值也是字符串,字符串是設備的狀態信息,可選的狀態如下所示:

(4)#address-cells 和 #size-cells 屬性

格式:

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>;   
    };   
};

(5)reg 屬性

reg 屬性的值,是一系列的 “address size”,用多少個 32 位的數來表示 address 和 size,由其父節點的# address-cells、#size-cells 決定。示例:

/dts-v1/;   
/ {   
    # address-cells = <1>;   
    # size-cells = <1>;   
    memory {   
     reg = <0x80000000 0x20000000>;   
    };   
};

(7)name 屬性

過時了,建議不用。它的值是字符串,用來表示節點的名字。在跟 platform_driver 匹配時,優先級最低。compatible 屬性在匹配過程中,優先級最高。

(8)device_type 屬性

過時了,建議不用。它的值是字符串,用來表示節點的類型。在跟 platform_driver 匹配時,優先級爲中。compatible 屬性在匹配過程中,優先級最高。

3、常用的節點

(1)根節點

用 / 標識根節點,如:

/dts-v1/;   
/ {   
    model = "SMDK24440";   
    compatible = "samsung,smdk2440";   
 
    # address-cells = <1>;   
    # size-cells = <1>;   
};

(2)CPU 節點

一般不需要我們設置,在 dtsi 文件中都定義好了,如:

cpus {   
    # address-cells = <1>;   
    # size-cells = <0>;   
 
    cpu0: cpu@0 {   
     .......   
    }   
};

(3)memory 節點

芯片廠家不可能事先確定你的板子使用多大的內存,所以 memory 節點需要板廠設置,比如:

memory {   
    reg = <0x80000000 0x20000000>;   
};

(4)chosen 節點

我們可以通過設備樹文件給內核傳入一些參數,這要在 chosen 節點中設置 bootargs 屬性:

chosen {   
    bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";   
};

操作設備樹的函數

Linux 內核給我們提供了一系列的函數來獲取設備樹中的節點或者屬性信息,這一系列的函數都有一個統一的前綴 “of_”(“open firmware” 即開放固件。),所以在很多資料裏面也被叫做 OF 函數。

1、節點相關操作函數

Linux 內核使用 device_node 結構體來描述一個節點,此結構體定義在文件 include/linux/of.h 中,定義如下:

與查找節點有關的 OF 函數有 5 個:

(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);

(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);

(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);

(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);

(5)of_find_node_by_path 函數

of_find_node_by_path 函數通過路徑來查找指定的節點,函數原型如下:

inline struct device_node *of_find_node_by_path(const char *path);

2、提取屬性值的 OF 函數

Linux 內核中使用結構體 property 表示屬性,此結構體同樣定義在文件 include/linux/of.h 中,內容如下:

Linux 內核也提供了提取屬性值的 OF 函數 :

(1) of_find_property 函數

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

property *of_find_property(const struct device_node *np,const char *name,int *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);

(3)讀取 u8、 u16、 u32 和 u64 類型的數組數據

(4)讀取 u8、 u16、 u32 和 u64 類型屬性值

(5)of_property_read_string 函數

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

int of_property_read_string(struct device_node *np,const char *propname,const char **out_string)
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/BI23d71SJkYPHnzQbFyvpQ