裝置樹解析函式

lethe1203發表於2024-03-23
參考資料:
https://www.bilibili.com/video/BV1fJ411i7PB?p=23&vd_source=432ba293ecfc949a4174ab91ccc526d6
https://blog.csdn.net/qq_40937426/article/details/107706460
https://blog.csdn.net/qq_41709234/article/details/129758010
裝置樹描述了裝置的詳細資訊,這些資訊包括數字型別的、字串型別的、陣列型別的,我們在編寫驅動的時候需要獲取到這些資訊。Linux 核心給我們提供了一系列的函式來獲取裝置樹中的節點或者屬性資訊,這一系列的函式都有一個統一的字首“of_”,所以在很多資料裡面也被叫做 OF 函式,這些 OF 函式原型都定義在 include/linux/of.h 檔案中。
Linux核心使用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 屬性   */ 
    struct  device_node *parent;     /* 父節點       */ 
    struct  device_node *child;      /* 子節點       */ 
    struct  device_node *sibling; 
    struct  kobject kobj; 
    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 
};

檢視節點相關OF函式

1、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 函式

透過 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 函式

根據 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 匹配表,此 OF 匹配表儲存著一些 compatible 值,因此可以透過 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 函式

透過路徑來查詢指定的節點:
inline struct device_node *of_find_node_by_path(const char *path)

// 引數說明 path:帶有全路徑的節點名,可以使用節點的別名,比如“
/backlight”就是 backlight 這個節點的全路徑。 返回值:找到的節點,如果為 NULL 表示查詢失

查詢父/ 子節點的OF 函式

1、of_get_parent 函式

用於獲取指定節點的父節點(如果有父節點的話):
struct device_node *of_get_parent(const struct device_node *node)

// 引數說明
node:要查詢的父節點的節點。
返回值:找到的父節點。

2、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 中:
struct property {
    char    name;            /*屬性名字      */
    int length;               /* 屬性長度      */
    void    value;           /* 屬性值       */
    struct property next;    /* 下一個屬性   */
    unsigned long _flags;
    unsigned int unique_id;
    struct bin_attribute attr;
};

1、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 函式

用於獲取屬性中元素的數量,比如 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 函式

從屬性中獲取指定標號的 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 表示屬性值列表太小。

字串和數值解析函式

1、陣列解析函式

int of_property_read_u8_array(const struct device_node    *np, 
                                               const char          *propname,   
                                               bu8                       *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 表示屬性值列表太小。

2、整體性屬性解析函式

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 表示屬性值列表太小。 

3、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,讀取成功,負值,讀取失敗。

4、of_n_addr_cells 函式

int of_n_addr_cells(struct device_node *np)

// 引數說明
np:裝置節點。
返回值:獲取到的#address-cells 屬性值。

5、of_n_size_cells 函式

int of_n_size_cells(struct device_node *np)

// 引數說明
np:裝置節點。
返回值:獲取到的#size-cells 屬性值。

其他常用的 OF 函式

1、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 函式

用於獲取地址相關屬性,主要是“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 函式

負責將從裝置樹讀取到的地址轉換為實體地址:
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結構體描述的都是裝置資源資訊。
本質上就是獲取 reg 屬性值,然後將其轉換為 resource 結構體型別:
int of_address_to_resource(struct device_node  *dev,
            int          index,
            struct resource      *r)

// 引數說明
dev:裝置節點。
index:地址資源標號。
r:得到的 resource 型別的資源值。
返回值:0,成功;負值,失敗。

5、of_iomap 函式

用於直接記憶體對映,以前我們會透過 ioremap 函式來完成實體地址到虛擬地址的對映,採用裝置樹以後就可以直接透過 of_iomap 函式來獲取記憶體地址所對應的虛擬地址,不需要使用 ioremap 函式了。
of_iomap 函式本質上也是將 reg 屬性中地址資訊轉換為虛擬地址,如果 reg 屬性有多段的話,可以透過 index 引數指定要完成記憶體對映的是哪一段。
void __iomem *of_iomap(struct device_node *np, int index)

// 引數說明
np:裝置節點。
index:reg 屬性中要完成記憶體對映的段,如果 reg 屬性只有一段的話 index 就設定為 0。
返回值:經過記憶體對映後的虛擬記憶體首地址,如果為 NULL 的話表示記憶體對映失敗。

fwnode和device_node的區別:

在/drivers/of/property.h中,有以下定義:
const struct fwnode_operations of_fwnode_ops = {
    .get = of_fwnode_get,
    .put = of_fwnode_put,
    .device_is_available = of_fwnode_device_is_available,
    .device_get_match_data = of_fwnode_device_get_match_data,
    .property_present = of_fwnode_property_present,
    .property_read_int_array = of_fwnode_property_read_int_array,
    .property_read_string_array = of_fwnode_property_read_string_array,
    .get_parent = of_fwnode_get_parent,
    .get_next_child_node = of_fwnode_get_next_child_node,
    .get_named_child_node = of_fwnode_get_named_child_node,
    .get_reference_args = of_fwnode_get_reference_args,
    .graph_get_next_endpoint = of_fwnode_graph_get_next_endpoint,
    .graph_get_remote_endpoint = of_fwnode_graph_get_remote_endpoint,
    .graph_get_port_parent = of_fwnode_graph_get_port_parent,
    .graph_parse_endpoint = of_fwnode_graph_parse_endpoint,
};
EXPORT_SYMBOL_GPL(of_fwnode_ops);

觀察下面函式:
static int of_fwnode_property_read_int_array(const struct fwnode_handle *fwnode,
                         const char *propname,
                         unsigned int elem_size, void *val,
                         size_t nval)
{
    const struct device_node *node = to_of_node(fwnode);

    if (!val)
        return of_property_count_elems_of_size(node, propname,
                               elem_size);

    switch (elem_size) {
    case sizeof(u8):
        return of_property_read_u8_array(node, propname, val, nval);
    case sizeof(u16):
        return of_property_read_u16_array(node, propname, val, nval);
    case sizeof(u32):
        return of_property_read_u32_array(node, propname, val, nval);
    case sizeof(u64):
        return of_property_read_u64_array(node, propname, val, nval);
    }

    return -ENXIO;
}
fwnode的解析函式最終呼叫到了of的解析函式,兩者沒有使用差異,看個人習慣使用
fwnode解析函式這裡不敘述

相關文章