Linux 內核:利用of_函數讀取設備樹結點/屬性信息
背景
設備樹描述了設備的詳細信息,這些信息包括數字類型的、字符串類型的、數組類型的,我們在編寫驅動的時候需要獲取到這些信息。
Linux 內核給我們提供了一系列的函數來獲取設備樹中的節點或者屬性信息,這一系列的函數都有一個統一的前綴“of_”,所以在很多資料里面也被叫做 OF 。
這些 OF原型都定義在include/linux/of.h
文件中。
我們看看在實際中如何使用設備樹。
參考:
- https://blog.csdn.net/qq_43121830/article/details/104866557
- https://blog.csdn.net/qq_35031421/article/details/105107629
判斷根節點兼容性OF函數
在Linux內核中,常常使用如下OF函數來判斷根節點的兼容性:
int of_machine_is_compatible(const char *compat);
DT設備的.dt_compat可能包含多個電路板,通過該函數可以判斷根節點compatible
的屬性,當該函數的compat
參數與根據點的compatible
匹配時,返回一個正整數。
用法,例如:
of_machine_is_compatible("samsung,exynos4");
查找節點的 OF
設備都是以節點的形式“掛”到設備樹上的,因此要想獲取這個設備的其他屬性信息,必
須先獲取到這個設備的節點。 Linux 內核使用 device_node 結構體來描述一個節點,此結構體定義在文件 include/linux/of.h
中,代碼如下:
struct device_node {
const char *name; /*節點的名字*/
const char *type; /*設備類型,來自節點中的device_type屬性, 如果沒有該屬性, 則設為"NULL"*/
phandle phandle;
const char *full_name; /*節點的全名,node-name[@unit-address]*/
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
};
與查找節點相關的,有如下函數。
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:
要查找的節點名字。
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 表示查找失敗。
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 表示查找失敗
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 表示查找失敗
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
of_get_parent
of_get_parent
用於獲取指定節點的父節點,原型如下:
struct device_node *of_get_parent(const struct device_node *node);
函數參數和返回值含義如下:
node:
需要查找父節點的子節點;返回值:
返回父節點;
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_get_next_child
節點的屬性信息里面保存了驅動所需要的內容,因此對於屬性值的提取非常重要, 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;
};
提取屬性值
Linux 內核也提供了提取屬性值的 OF。
of_find_property
of_find_property
用於查找指定的屬性,函數原型如下:
property *of_find_property(const struct device_node *np,
const char *name,
int *lenp);
函數參數和返回值含義如下:
np:
設備節點;name:
屬性名字;lenp:
屬性值的字節數;
返回值:
找到的屬性。
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:
元素長度。
返回值:
得到的屬性元素數量。
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 表示屬性值列表太小。
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 表示屬性值列表太小。
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 表示屬性值列表太小。
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,讀取成功,負值,讀取失敗。
of_n_addr_cells
of_n_addr_cells用於獲取 #address-cells
屬性值,函數原型如下:
int of_n_addr_cells(struct device_node *np);
函數參數和返回值含義如下:
np:
設備節點。返回值:
獲取到的#address-cells 屬性值。
of_n_size_cells
of_size_cells
用於獲取 #size-cells
屬性值,函數原型如下:
int of_n_size_cells(struct device_node *np);
函數參數和返回值含義如下:
np:
設備節點。
返回值:
獲取到的#size-cells 屬性值。
其他常用的 OF
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 的話表示讀取失敗。
of_translate_address
of_translate_address負責將從設備樹讀取到的地址轉換為物理地址,函數原型如下:
u64 of_translate_address(struct device_node *dev,
const __be32 *in_addr);
函數參數和返回值含義如下:
dev:
設備節點。in_addr:
要轉換的地址。
返回值:
得到的物理地址,如果為 OF_BAD_ADDR 的話表示轉換失敗。
of_address_to_resource
resource結構體描述的都是設備資源信息, resource 結構體定義在文件include/linux/ioport.h 中,定義如下:
struct resource {
resource_size_t start; /*起始地址,對於32位soc,resource_size_t 的數據類型是u32*/
resource_size_t end; /*結束地址*/
const char *name; /*資源的名字*/
unsigned long flags; /*資源標志位,一般表示資源類型,可選的資源標志定義在文件 include/linux/ioport.h,如IORESOURCE_BITS、IORESOURCE_MEM、IORESOURCE_IRQ等 */
struct resource *parent, *sibling, *child;
};
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,成功;負值,失敗。
of_iomap
of_iomap
用於直接內存映射,以前我們會通過 ioremap
來完成物理地址到虛擬地址的映射,采用設備樹以后就可以直接通過 of_iomap
來獲取內存地址所對應的虛擬地址,不需要使用 ioremap了。該函數的原型如下:
void __iomem *of_iomap(struct device_node *np,
int index);
函數參數和返回值含義如下:
np:
設備節點。index:
reg 屬性中要完成內存映射的段,如果 reg 屬性只有一段的話 index 就設置為 0。
返回值:
經過內存映射后的虛擬內存首地址,如果為 NULL 的話表示內存映射失敗。
例子
目的:期望通過linux/of.h
中相關函數,在驅動中讀取設備樹相關結點信息和屬性信息
要求:
- ①讀取設備樹
/backlight
結點下的屬性,以及屬性信息,合理處理返回值和錯誤信息 - ②設計一個能夠讀取
u32
類型屬性的通用函數,並在init
函數中輸出信息
設備樹
/ {
backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 5000000>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
status = "okay";
wp-inverted ;
};
};
驅動程序
/* 此文件為linux 內核 of函數測試文件
* 實驗目的:在init函數中使用of函數讀取設備樹中的 根節點下xxx設備節點信息
* 其路徑為:
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_device.h>
#include <linux/slab.h>
struct device_node* ofdts_node;
struct property* blprop;
int ret = 0;
const char* out_string;
int result = 0;
int mem_count = 0;
u32* out_values;
static u32 show_members(int mem_count, char* prop_name)
{
u8 i;
if(mem_count < 0){
printk("fail_property_count_elems_of_size!!!\n");
return -ENODEV;
}
else if (mem_count == 0)
{
printk("%s中有%d個元素\n",prop_name, mem_count);
}else if(mem_count == 1){
printk("%s中有%d個元素\n",prop_name, mem_count);
out_values = kmalloc(sizeof(u32) * mem_count,GFP_KERNEL);
if(!out_values){
printk("fail_mem\n");
return -ENODEV;
}
ret = of_property_read_u32(ofdts_node, prop_name, out_values);
if(ret < 0){
printk("fail_read_u32!!!\n");
kfree(out_values);
return -ENODEV;
}
printk("%s中的元素為:%d\n",prop_name, *out_values);
}
else{
printk("%s中有%d個元素\n",prop_name, mem_count);
out_values = kmalloc(sizeof(u32) * mem_count,GFP_KERNEL);
ret = of_property_read_u32_array(ofdts_node, prop_name, out_values,mem_count);
if(ret < 0){
printk("fail_read_u32_array!!!\n");
kfree(out_values);
return -ENODEV;
}
printk("%s中有元素為:",prop_name);
for(i = 0; i < mem_count; i++){
printk("%d\t",out_values[i]);
}
}
return 0;
}
static int __init ofdts_init(void)
{
printk("\nofdts_initing.........\n");
/* 0. 提取 backlight 節點*/
ofdts_node = of_find_node_by_name(NULL, "backlight");
if(!ofdts_node){
goto fail_node;
}else{
printk("0. 獲取節點:%s 成功~\n", ofdts_node->name);
}
/* 1. 獲取compatible = "pwm-backlight" 和 status = "okay"這倆都是字符串類型的,使用兩個不同的函數分別測試*/
// 1 compatible
ret = of_property_read_string(ofdts_node, "compatible", &out_string);
if(ret < 0){
goto fail_property_read_string;
}else{
printk("1.0 獲取compatible成功:%s\n", out_string);
}
// 2 status
blprop = of_find_property(ofdts_node, "status", NULL);
if(!blprop){
goto fail_find_property;
}else{
printk("1.1 獲取status成功:%s\n", (char*)blprop->value);
}
/* 2. 獲取default-brightness-level = <6>; */
mem_count = of_property_count_elems_of_size(ofdts_node, "default-brightness-level", sizeof(u32));
result = show_members(mem_count, "default-brightness-level");
if(result < 0){
return result;
}
/* 3. brightness-levels = <0 4 8 16 32 64 128 255>; */
mem_count = of_property_count_elems_of_size(ofdts_node, "brightness-levels", sizeof(u32));
result = show_members(mem_count, "brightness-levels");
printk("ofdts_init.........OK!!!\n");
return result;
fail_find_property:
fail_property_read_string:
printk("fail_property_read_string!!!\n");
return -ENODEV;
fail_node:
printk("fail_find_node_byname!!!\n");
return -ENODEV;
}
static void __exit ofdts_exit(void)
{
kfree(out_values);
printk("ofdts_exit.........OK!!!\n");
}
/* 注冊函數 */
module_init(ofdts_init);
module_exit(ofdts_exit);
/* license和作者*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("QJY");
注意:程序能夠測試通過,可能返回值處理以及錯誤處理有不合理之處,作者並未排查,本程序僅作為練習測試使用;
測試結果:
lib/modules/4.1.15 #modprobe ofdts
ofdts_initing. . . .. . . . .
0.獲取節點: backlight成功~
1.0獲取compatible成功:pwm-backlight
1.1 獲取status成功:okay
default-brightness-level中有1個元素
default-brightness-level中的元素為:6
brightness-levels中有8個元素
brightness-levels中有元素為:0 4 8 16 32 64 128 255
ofdts_init....OK!!