Linux驱动开发笔记(五)——设备树(下)——OF函数
一、OF函数定义
第6.8讲 Linux设备树详解-绑定文档以及OF函数_哔哩哔哩_bilibili
《指南》43.9部分
设备树的功能就是描述设备信息,帮助驱动开发。那么驱动如何获取设备信息?获取这些信息的函数linux直接提供,都定义在linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include/linux/of.h文件中。这些函数统一以of开头,也称为OF函数。
1.1 查找节点
设备树上的设备都是一个个节点。要获取某个设备信息,需要先找到这个设备节点。Linux内核使用device_node结构体来描述一个节点:
// 定义在linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include/linux/of.h
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
};
1.1.1 of_find_node_by_name
通过名字查找节点。名字是完整的node-name@unit-address,不是只有前半部分node-name,也不是label。
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
// from: 从哪个节点开始查找。为NULL表示从根节点开始查找整个设备树
// name: 要查找节点的名字。名字不要包含label!
// return: 找到的节点。返回NULL表示未找到
1.1.2 of_find_node_by_type
通过device_type类型查找。不过现在device_type已经弃用,这个函数也很少再用。
struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
// from: 从哪个节点开始查找。为NULL表示从根节点开始查找整个设备树
// type: 要查找节点的device_type字符串
// return: 找到的节点。返回NULL表示未找到
1.1.3 of_find_compatible_node
通过兼容性列表compatible查找
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compat);
// from: 从哪个节点开始查找。为NULL表示从根节点开始查找整个设备树
// type: 要查找节点的device_type字符串。可以为NULL,表示忽略device_type属性
// compat: 要查找节点的compatible属性列表
// return: 找到的节点。返回NULL表示未找到
// eg: struct device_node *node;node = of_find_compatible_node(NULL, NULL, "arm,cortex-a7"); // 查找整个设备树上兼容arm,cortex-a7的设备节点
1.1.4 of_find_matching_node_and_match
通过匹配列表查找
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
// return: 找到的节点。返回NULL表示未找到
1.1.5 of_find_node_by_path
通过路径查找。这个路径指的是设备节点在设备树中的路径,不是文件路径。
static inline struct device_node *of_find_node_by_path(const char *path)
// path: 带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是根节点下backlight节点的全路径。
// return: 找到的节点。返回NULL表示未找到
//eg:
struct device_node *np;
np = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/serial@02020000");
1.2 查找父/子节点
1.2.1 of_get_parent
获取指定节点的父节点
struct device_node *of_get_parent(const struct device_node *node)
// node : 要查找父节点的节点
// return: 找到的父节点。NULL表示未找到
1.2.2 of_get_next_child
获取指定节点的子节点
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
// node : 要查找子节点的节点
// prev : 前一个子节点。一个节点下有很多子节点,可以设置从哪一个子节点开始查找
// 可以设置为NULL,表示从第一个子节点开始
// return: 找到的子节点
1.3 提取属性值
通过前面两类函数找到了目标节点,现在可以开始获得指定节点的具体属性了。
1.3.1 of_find_property
查找指定的属性。
static inline struct property *of_find_property(const struct device_node *np,const char *name, int *lenp)
// np : 节点
// name : 属性名
// lenp : 属性值的字节长度,一般写NULL即可
// return: 找到的属性
Linux内核中使用结构体property表示属性,其中property结构为:
struct property {char *name; // 属性名int length; // 长度void *value; // 值struct property *next; // 下一个属性unsigned long _flags;unsigned int unique_id;struct bin_attribute attr;
};
1.3.2 of_property_count_elems_of_size
获取属性中元素的数量。比如reg属性值(如reg = <0x80000000 0x20000000>;)是一个数组,那么使用此函数可以获取到这个数组的大小(2)。
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
// np : 节点
// proname : 需要统计元素数量的属性名字
// elem_size:每一个元素的size
// return : 得到的属性元素数量
1.3.3 of_property_read_u32_index
用于从属性中获取指定索引的u32类型数据值。
比如某个属性有多个u32类型的值,那么就可以使用此函数来获取指定索引的数据值。
static inline int of_property_read_u32_index(const struct device_node *np,const char *propname, u32 index, u32 *out_value)
// np : 节点
// proname : 要读取的属性名
// index : 要读取的值的索引
// out_value: 读取到的值
// return : 0成功。负值失败:-EINVAL属性不存在,-ENODATA表示要读取的数据,-EOVERFLOW属性值列表太小
1.3.4 of_property_read_u8_array
用于读取一个u8类型数组属性的所有数据。
类似的函数还有of_property_read_u16_array、of_property_read_u32_array、of_property_read_u64_array,表示不同的数组类型。
int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz)
// np : 节点
// propname : 要读取的属性名
// out_values: 读取到的数组
// sz : 要读取的数组元素数量
// return : 0成功。负值失败:-EINVAL属性不存在,-ENODATA没有要读取的数据,-EOVERFLOW属性值列表太小。
1.3.5 of_property_read_u8
除了数组属性以外,更多的属性还是只有一个整形值。该函数用于读取这种只有一个u8整形值的属性。
类似的函数还有of_property_read_u16、of_property_read_u32、of_property_read_u64。
int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz)
// np : 节点
// propname : 要读取的属性名
// out_values: 读取到的值
// return : 0成功。负值失败:-EINVAL属性不存在,-ENODATA没有要读取的数据,-EOVERFLOW属性值列表太小。
1.3.6 of_property_read_string
用于读取字符串类型属性的值
int of_property_read_string(struct device_node *np,const char *propname, const char **out_string)
// np : 节点
// propname : 要读取的属性名
// out_string: 读取到的字符串
// return : 0成功。负值失败
1.3.7 of_n_addr_cells
获取#size-cells的值
int of_n_addr_cells(struct device_node *np);
// np : 节点
// return: 获取到的#size-cells的值
1.3.8 of_n_size_cells
获取#size-cells的值
int of_n_size_cells(struct device_node *np)、
// np : 节点
// return: 获取到的#size-cells的值
二、OF函数实际使用
第6.9讲 Linux设备树详解-OF函数操作实验_哔哩哔哩_bilibili
2.1 文件结构
新建实验4文件夹4_dtsof,直接将之前实验3文件夹3_newchrled里的Makefile、newchrled.c、.vscode复制到新的文件夹里。将newchrled.c改名为dtsof.c。
用vscode打开4_dtsof,将工作区另存为。
4_DTSOF (工作区)
├── .vscode
│ ├── c_cpp_properties.json
│ └── settings.json
├── 4_dtsof.code-workspace
├── Makefile
└── newchrled.c
将Makefile中的obj-m修改为obj-m := dtsof.o
2.2 dtsof.c
2.2.1获取backlight的compatible属性值
imx6ull-alientek-emmc.dts中backlight的定义如下:
backlight {compatible = "pwm-backlight";pwms = <&pwm1 0 5000000>;brightness-levels = <0 4 8 16 32 64 128 255>;default-brightness-level = <6>;status = "okay";};
编写dtsof.c,获取backlight的compatible属性值:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/of.h>/* 模块入口 */
static int __init dtsof_init(void){int ret = 0;struct device_node *backlight_nd; // 节点指针struct property *comppro; // 属性指针// 查找backlight节点 // 路径为/backlight 定义在linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/boot/dts/imx6ull-alientek-emmc.dtsbacklight_nd = of_find_node_by_path("/backlight"); //路径查找if(backlight_nd == NULL){ // 失败ret = -EINVAL;goto fail_findnd; // 错误处理}// 查找backlight属性comppro = of_find_property(backlight_nd, "compatible", NULL); // 属性名查找if(comppro == NULL){ret = -EINVAL;goto fail_finpro; // 错误处理} else {printk("compatible = %s\r\n",(char*)comppro->value);}return 0;fail_finpro: // 查找属性失败
fail_findnd: // 查找节点失败printk("failed\r\n");return ret;
}/* 模块出口 */
static void __exit dtsof_exit(void){}/* 注册入口出口*/
module_init(dtsof_init);
module_exit(dtsof_exit);
MODULE_LICENSE("GPL");
# VSCODE终端
make
sudo cp dtsof.ko /home/for/linux/nfs/rootfs/lib/modules/4.1.15/ -f# 串口
cd lib/modules/4.1.15/
modprobe dtsof.ko
depmod
modprobe dtsof.ko # 可以看到输出:compatible = pwm-backlight
rmmod dtsof.ko
2.2.2获取backlight的所有属性值
上面的代码只简单演示了一下使用OF函数获取compatible属性值。要获取backlight的所有属性值,完整代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/slab.h>/* 模块入口 */
static int __init dtsof_init(void){int ret = 0;struct device_node *backlight_nd;// 节点指针struct property *comppro; // 保存compatible属性const char* status; // 保存status属性u32 default_brightness_level; // 保存default-brightness-level属性u32 count = 0; // 保存brightness-levels属性的元素数量u32 *brightness_levels; // 保存brightness-levels属性的数据u8 i = 0; // 给for循环用的// 1.查找backlight节点======================================================================// 路径为/backlight // 定义在linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/boot/dts/imx6ull-alientek-emmc.dtsbacklight_nd = of_find_node_by_path("/backlight"); //路径查找if(backlight_nd == NULL){ // 失败ret = -EINVAL;goto faile_findnd;}// 2.查找backlight属性======================================================================// 获取compatible属性(属性名查找)comppro = of_find_property(backlight_nd, "compatible", NULL);if(comppro == NULL){ret = -EINVAL;goto fail_finpro;} else {printk("compatible = %s\r\n",(char*)comppro->value);}// 获取status属性(读取字符串)ret = of_property_read_string(backlight_nd, "status", &status);if(ret < 0){goto fail_finprs;} else {printk("status = %s\r\n", status);}// 获取default-brightness-level属性(读取u32)ret = of_property_read_u32(backlight_nd, "default-brightness-level", &default_brightness_level);if(ret < 0){goto fail_read32;} else {printk("default-brightness-level = %d\r\n", default_brightness_level);}// 获取brightness-levels的元素个数(读取元素个数)count = of_property_count_elems_of_size(backlight_nd, "brightness-levels", sizeof(u32));if(count < 0){goto fail_readele;} else {printk("brightness-level elems size = %d\r\n", count);// 获取brightness-levels的数据(读取u32数组)brightness_levels = kmalloc(count * sizeof(u32), GFP_KERNEL); // 内存申请if(!brightness_levels){ goto fail_mem;}ret = of_property_read_u32_array(backlight_nd, "brightness-levels", brightness_levels, count);if(ret < 0){goto fail_readarr; // 错误处理统一放到goto里面去,因此这里不释放内存,而是放到goto去处理} else {printk("brightness-level elems = ");for(i=0;i<count;i++){printk("%d ",brightness_levels[i]);}printk("\r\n");kfree(brightness_levels);}}return 0;/* 错误处理 */
// 这里goto只是演示一下格式,并不详细处理fail_readarr: // 读取brightness-levels数据失败kfree(brightness_levels); //释放内存
fail_mem: // 内存分配失败
fail_readele: // 读取元素数量失败
fail_read32: // 读取default-brightness-level失败
fail_finprs: // 读取status失败
fail_finpro: // 查找compatible失败
faile_findnd: // 查找节点失败printk("failed\r\n");return ret;
}/* 模块出口 */
static void __exit dtsof_exit(void){}/* 注册入口出口*/
module_init(dtsof_init);
module_exit(dtsof_exit);
MODULE_LICENSE("GPL");
modprobe以后应当能看到以下内容: