【linux】驅動-8-一文解決設備樹


前言

8. Linux設備樹

Linux3.x 之後引入了設備樹,用於描述一個硬件平臺的板級細節。html

8.1 設備樹簡介

設備樹能夠被 bootloader(uboot)傳遞到內核,內核從中獲取設備樹中的硬件信息。node

設備樹的兩個特色linux

  • :以 樹狀結構 描述硬件資源。
  • :設備樹能夠像頭文件使用,一個設備樹文件引用另一個設備樹文件。

幾個經常使用的縮寫框架

  • 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 文件能夠定義一些宏。flex

/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

流程

  1. dts 在 PC 機上被編譯爲 dtb 文件。
  2. u-bootdtb 文件傳給內核。
  3. 內核解析 dtb 文件,把每個節點都轉換爲 device_node 結構體。
  4. 對於某些 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
  • 總線 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
  • matchesof_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

  • 如下函數分別讀取 八、1六、3二、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 開始。
  • rresource 結構體,獲得的地址信息(函數輸出參數)。
  • 返回:
    • 成功:0;
    • 失敗:失敗錯誤碼。

of_iomap

  • 函數原型:void __iomem *of_iomap(struct device_node *np, int index)
  • 源碼路徑:內核源碼/include/linux/of.h
  • np:設備節點。
  • index:指定映射那一段內存。一般狀況下,reg 屬性包含多段。標號從 0 開始。
  • 返回:
    • 成功:轉換後的地址。
    • 失敗:NULL。
相關文章
相關標籤/搜索