Linux驱动开发笔记(五)——设备树(下)——OF函数

发布于:2025-07-30 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、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
└── dtsof.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.dts
    backlight_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.dts
    backlight_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以后应当能看到以下内容: 


网站公告

今日签到

点亮在社区的每一天
去签到