• Linux驱动开发四.使用of函数获取设备树节点信息


    这一章之前我们应该先大致了解一下设备树的基本信息和基础语法。这两点我就先不总结了,网上多的是。为了后续做设备树的使用,我们这一章讲一下如何在驱动中(内核态)获取设备树中对于设备的描述。

    of函数

    内核为我们提供了一组of函数,用来获取设备树节点中的详细信息。这个OF函数路径为在内核路径下单include/linux/of.c中。我们主要使用下面几种

    1. 查找节点
    2. 查找子/父节点
    3. 提取属性值
    4. 其他,例如查询属性是否包含指定字符的、直接获取内存映射信息的等等

    注意一点:在内核中如果想要使用of函数,要在导入的of.h内进行配置,加上下面的代码

    #ifdef CONFIG_OF

    如下图所示

    结合下面一段设备树的信息,我们来试下如何使用这些of函数

    #include <dt-bindings/input/input.h>
    #include "imx6ull.dtsi"
    
    / {
        model = "Freescale i.MX6 ULL 14x14 EVK Board";
        compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
    
        chosen {
            stdout-path = &uart1;
        };
    
        memory {
            reg = <0x80000000 0x20000000>;
        };
    
        reserved-memory {
            #address-cells = <1>;
            #size-cells = <1>;
            ranges;
    
            linux,cma {
                compatible = "shared-dma-pool";
                reusable;
                size = <0x14000000>;
                linux,cma-default;
            };
        };
    
        backlight {
            compatible = "pwm-backlight";
            pwms = <&pwm1 0 5000000>;
            brightness-levels = <0 4 8 16 32 64 128 255>;
            default-brightness-level = <6>;
            status = "okay";
        };
    }

    我们主要想要获取根节点下面back-light的信息。

    通过OF函数获取节点

    节点(device_node)在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 properties */
        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
    };

    驱动操作硬件,主要是获取节点内的属性。而想要获取属性,首先要索引到该节点。这个device_node结构体就是被索引出来的节点对象。

    比如我们需要操作设备树中backlight这个设备,因为我们有这个设备的名称(backlight),并且这个设备树是我们自己写的,所以路径也是明确的,我们只需要直接指定路径就可以了,就要用到下面的函数

    static inline struct device_node *of_find_node_by_path(const char *path)

    参数path就是要操作的节点路径,由于backlight是在根节点下面,就可以直接指定,所以这个函数是我们最长用到。但是如果我们不太清楚其具体路径,还有个通过指定名称来查找的

    extern struct device_node *of_find_node_by_name(struct device_node *from,
        const char *name);

    指针参数from是我们需要查找的起始路径,name就是我们要找到设备树名称。

    此外还有几种不太常用的,在/include/linux/of.h文件中都能找到

    #define for_each_of_allnodes_from(from, dn) \
        for (dn = __of_find_all_nodes(from); dn; dn = __of_find_all_nodes(dn))
    #define for_each_of_allnodes(dn) for_each_of_allnodes_from(NULL, dn)
    extern struct device_node *of_find_node_by_name(struct device_node *from,
        const char *name);
    extern struct device_node *of_find_node_by_type(struct device_node *from,
        const char *type);
    extern struct device_node *of_find_compatible_node(struct device_node *from,
        const char *type, const char *compat);
    extern 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);
    
    extern struct device_node *of_find_node_opts_by_path(const char *path,
        const char **opts);

    不太常用,这里就不再一一列举了。

    获取父子节点

    这个功能暂时还用不到,把头文件中的声明列出来,以后如果需要了再做完善

    extern struct device_node *of_get_parent(const struct device_node *node);
    extern struct device_node *of_get_next_parent(struct device_node *node);
    extern struct device_node *of_get_next_child(const struct device_node *node,
                                         struct device_node *prev);
    extern struct device_node *of_get_next_available_child(const struct device_node *node, 
                                     struct device_node *prev); extern struct device_node *of_get_child_by_name(const struct device_node *node,                  const char *name);

    不再说明具体用法。

    获取节点属性

    敲黑板!这段是重点!!!

    我们要通过设备树来对应硬件写驱动,必须要拿到设备树里这些节点中指定键的值。根据值不同数据类型我们一步步来实现

    字符串类属性获取

    backlight属性中compatible(兼容性)属性的值时字符串类型

    compatible = "pwm-backlight";
     如果我们要在内核中判定写的驱动是否兼容该设备树节点对应设备,就要读取该属性的值进行判定,这时需要用到下面的函数
    extern int of_property_read_string(struct device_node *np,
                       const char *propname,
                       const char **out_string);

    参数np就是我们在前面拿到的节点结构体,propame就是属性,out_string就是要读取的数据,通过指针放到内存中。

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/uaccess.h>
    #include <linux/io.h>
    #include <linux/types.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    #include <linux/of.h>
    #include <linux/of_address.h>
    #include <linux/of_irq.h>
    
    
    static int __init dtsof_init(void)
    {
        int ret = 0;
        struct device_node *dev_nd=NULL;
        const char *str;
    
        //获取节点
        dev_nd = of_find_node_by_path("/backlight");
        //找到backlight的节点,其路径为/backlight
        if(dev_nd==NULL){
            ret = -EINVAL;
            return ret;
            goto fail_findnd;
        }
    
        //获取字符串类型属性
        ret = of_property_read_string(dev_nd,"compatible",&str);
        if(ret<0)
        {
            goto fail_readstr;
        }
        else{
            printk("read string status=%s\r\n",str);
        }
    
        return ret;
    fail_readstr:
    fail_findnd:
        return ret;
    }
    
    static void __exit dtsof_exit(void)
    {
    
    }
    
    module_init(dtsof_init);
    module_exit(dtsof_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("ZZQ");

    整个文件是以驱动的流程呈现的,通过module_init()将初始化函数dtsof_init注册,在加载模块以后,先获取backlight对应节点(dev_nd),再通过读取字符属性的函数将compatible属性值读取到str指向的内存中,最后通过printk打印至终端。修改make文件后编译生成ko文件。

    将ko文件复制到rootfs路径下lib/modules/4.1.15下,加载模块,会直接打印出来(第一次加载模块一定注意先运行depmod一下!),就会有下面的效果

     of函数在读取成功时返回值应该为0,否则返回值为负数,所以在程序中我加了个判定,如果有异常通过goto语句跳转至相应功能代码处(案例中不需要做相应动作,所以是空的。)

    数值类型属性读取
    这里我们来读取backlight节点中下面的属性
    default-brightness-level = <6>;

    要注意点是,这里我们读取的数值类型数据都是32位的,要用到下面的函数

    static inline int of_property_read_u32(const struct device_node *np,
                                     const char *propname,
                                      u32 *out_value)

    其中参数np还是device_node,propname是我们要读取的属性,后面的指针是指向到读取数据存储的内存地址

    由于前面已经有了整个函数的架构,这里只把init函数放出来看下

    static int __init dtsof_init(void)
    {
        int ret = 0;
        struct device_node *dev_nd=NULL;
    
        const char *str;
    
        //获取节点
        dev_nd = of_find_node_by_path("/backlight");
        //找到backlight的节点,其路径为/backlight
        if(dev_nd==NULL){
            ret = -EINVAL;
            return ret;
            goto fail_findnd;
        }
    
        //获取字符串类型属性
        ret = of_property_read_string(dev_nd,"compatible",&str);
        if(ret<0)
        {
            goto fail_readstr;
        }
        else{
            printk("read string status=%s\r\n",str);
        }
    
        //获取数字类型属性
        const u32 def_val;
        ret = of_property_read_u32(dev_nd,"default-brightness-level",&def_val);
        if(ret<0){goto fail_read32;}
        else{
            printk("default-brightness-level=%d\r\n",def_val);
        }
    
        return ret;
    fail_read32:
    fail_readstr:
    fail_findnd:
        return ret;
    }

    make一下复制到指定目录,启动开发板后加载模块

    这样可以看到我们读取到数据了,整形数据,值为6!

    数组类型数据读取

    最后就是读取数组类型的数据,首先要用到下面的函数获取数组的长度

    static inline int of_property_count_elems_of_size(const struct device_node *np,
                const char *propname, int elem_size)

    注意那个elem_size是数组中每个元素的数据长度,我们可以直接用sizeof(u32)来指定

    static inline int of_property_read_u32(const struct device_node *np,
                           const char *propname,
                           u32 *out_value)

    参数device_node就是设备节点,propname为属性名称,最后的out_value是读取的数组存储的地方。

    先演示一个稍微复杂的方法,使用这个函数时可以先使用kmalloc函数申请一块内存(记得导入头文件(/linux/slab.h)),把读到的数据放在这块内存中

    static int __init dtsof_init(void)
    {
        int ret = 0;
        struct device_node *dev_nd=NULL;
        u8 i=0;
        u8 datasize;
        u32 *brival;
    
        //获取节点
        dev_nd = of_find_node_by_path("/backlight");
        //找到backlight的节点,其路径为/backlight
        if(dev_nd==NULL){
            ret = -EINVAL;
            return ret;
            goto fail_findnd;
        }
    
        // 获取数组类型属性
        
        datasize = of_property_count_elems_of_size(dev_nd,"brightness-levels",sizeof(u32));
        if(ret<0){
            ret = -EINVAL;
            goto fail_readele;
        }
        else{
            printk("brightness-levels elem size= %d\r\n",datasize);
        }
    
        brival = kmalloc(datasize * sizeof(u32),GFP_KERNEL);        //申请内存
        if(!brival){
            printk("buf err\r\n");
            ret = -EINVAL;
            goto fail_mem;
        }
    
        //内存获取成功,获取数组
        ret = of_property_read_u32_array(dev_nd,"brightness-levels",brival,datasize);
        if(ret<0){
            printk("read array err\r\n");
            goto fail_read32array;
        }
        else{
            for(i=0;i<datasize;i++)
            {printk("brightness-levels[%d]=%d\r\n",i,*(brival+i));}
    
        }
        kfree(brival);
        return ret;
    
    fail_read32array:
        kfree(brival);   //释放内存
    fail_mem:
    
    fail_readele:
    fail_findnd:
        return ret;
    }

    注意在使用kmalloc函数后如果成功申请了内存,在使用完成后要将该内存空间释放,有个和kmalloc成对的函数kfree,把申请的内存传递过去就可以了。make以后加载模块可以看出来读取到的值和数组。

    这种方法要事先申请一段内存空间,也可以直接声明一个数组,把数组直接传给参数。但是用上面的方法有个优点就是如果我们不知道数组元素的个数,在声明变量的时候是无法定义数组大小的,那么就可以直接通过获取数组元素个数再通过这个值申请内存空间。直接定义数组的过程在下一章节会使用到!

    有了上面的几个函数,后面我们就可以从最基础的点灯来演示一下了!

  • 相关阅读:
    常用的几个JavaScript调试技巧
    一个能够编写、运行SQL查询并可视化结果的Web应用:SqlPad
    一个能够编写、运行SQL查询并可视化结果的Web应用:SqlPad
    一个能够编写、运行SQL查询并可视化结果的Web应用:SqlPad
    67 个JavaScript和CSS实用工具、库与资源
    67 个JavaScript和CSS实用工具、库与资源
    67 个JavaScript和CSS实用工具、库与资源
    SQL日常维护的语句
    ACM2096_小明A+B
    Janet Wu price
  • 原文地址:https://www.cnblogs.com/yinsedeyinse/p/16347856.html
Copyright © 2020-2023  润新知