Linux驱动入门(6.1)LED驱动---设备树

简介: Linux驱动入门(6.1)LED驱动---设备树

前言

(1)在韦东山Linux驱动入门实验班(5)LED驱动—驱动分层和分离,平台总线模型我们已经讲解了如何将驱动程序和硬件程序进行剥离。但是大佬们感觉这样还不行,他们认为要专门弄一个结构存储硬件信息,而不是用c文件存储。于是,大佬们就发明了设备树进行存储硬件信息。

(2)代码仓库:gitee仓库;github仓库;

(3)注意:大家下载我仓库里面的代码再阅读本文会跟好理解一点。我仓库里面的代码依旧加上了详细的注释,觉得本文过于冗余。可以看我仓库代码和正点原子的文档学习。

(4)我这里将会使用BCompare软件进行文件对比,方便各位理解。


不同点

不同点1 — platform_driver配置不同

(1)同样,一个驱动程序还是需要从module_init()这个宏开始看起,然后网上爬,我们会发现platform_driver结构体有一个细微的区别。

(2)platform_driver.driver里面我们增加了一个of_match_table变量,led_dtb。这个是用于和设备树进行匹配的。关于驱动程序和设备匹配规则不太明白的同学,建议看一下 Linux驱动与设备的匹配规则。

(3)我们在of_match_table变量中需要传入const struct of_device_id类型的数组。这里里面的of_device_id结构体中的compatible值要和设备树里面的compatible 名字是一模一样的。否则无法匹配成功。

/*   在dts文件下增加这几行  */
/{
    test1:test_device_tree{
           compatible = "test_compatible";
           gpios = <&gpio5 3 GPIO_ACTIVE_HIGH>;
        };
}

不同点2 — 存放设备信息的结构体不同

(1)如果驱动和设备匹配上了,就会触发probe函数。在这个函数里面我们需要获得设备信息。如果是设备树,使用device_node指针可以获得。如果是c文件中的设备信息,使用resource结构体指针可以获得数据。

(2)如果匹配上了设备树np将会指向这个设备树文件。否则np将会是一个空指针。

struct device_node *np = pdev->dev.of_node;  //设备树中
struct resource *led_resource;  //c文件中

不同点3 — 获取gpio数量的方式不同

(1)在设备树中,直接调用of_gpio_count(np)可以获得gpio的数量。

(2)而在C文件中,有两种方式可以获得设备信息。


//设备树中统计的设备的 GPIO 数量
count = of_gpio_count(np); 
//C文件中获取GPIO数量的两种方法
count = pdev->num_resources; //这个不会区分GPIO的flag
while (1)  //这个可以获得指定flag的GPIO数量
{
  /* 下面这9行,是用于统计有多少个GPIO的
   * dev:一个指向 platform_device 结构的指针,表示要获取资源信息的设备。
   * type:一个无符号整数,表示要获取的资源类型。在 Linux 内核中,资源类型使用常量来表示,
          例如 IORESOURCE_MEM 表示内存资源,IORESOURCE_IRQ 表示中断资源等。你可以根据需要选择适当的资源类型。
   * num:一个无符号整数,表示要获取的资源的索引号。在一个设备中可能存在多个相同类型的资源,通过索引号可以区分它们。
   * 返回值:返回一个指向 resource 结构的指针,表示获取到的资源信息。
             resource 结构包含了资源的起始地址、大小等信息。如果没有找到指定的资源,函数将返回 NULL。
  */
  led_resource = platform_get_resource(pdev, IORESOURCE_IRQ, count);
  if (led_resource)
  {
    count++;
  }
  else
  {
    break;
  }
}


不同点4 — 获取引脚号的方式不同

(1)

<1>在设备树中,直接调用of_get_gpio()可以获得gpios的数据,然后并且将他转换为引脚号。

<2>例如本文中的<&gpio5 3 GPIO_ACTIVE_HIGH>,他会调用desc_to_gpio这个函数将gpio5 3转换为引脚号131。

(2)在c文件中,获取引脚信息需要调用platform_get_resource()函数获得platform_device.resource结构体,然后调用led_resource->start,就可以获得引脚号了。不过这个引脚号需要你本人来进行计算。所以相对来说麻烦一点。

//设备树
gpios[i].gpio = of_get_gpio(np, i);   //获得gpio信息
//c文件中
led_resource = platform_get_resource(pdev, IORESOURCE_IRQ, i);  //从节点里面取出第i项
if(led_resource==NULL)
{
  printk("platform_get_resource is flaut\r\n");
}
printk("platform_get_resource is ok\r\n");
gpios[i].gpio = led_resource->start;  //将需要操作的IO号传递给gpios[i].gpio

不同点5 — 获取引脚名字的方式不同

(1)说实话,其实也差不多。都是一个结构体指针指向name变量。为了方便新手理解,还是当成不同来处理算了。

//设备树
sprintf(gpios[i].name, "%s", np->name);  //将设备树中的节点名字传递给gpios[i].name
//c文件
sprintf(gpios[i].name, "%s", pdev->name); //将platform_device.resource.name传递给gpios[i].name

OF函数介绍

(1)Linux的设备树起源于OpenFirmware,OF。所以与设备树相关的函数前缀都有OF,也就是OF函数。

(2)我们驱动如何想获取设备树的信息,就需要调用OF函数。OF函数需要先找到节点,然后才可以获取属性。所以我这里将会分为获得节点和获得属性两部分讲解。



使用OF函数获得节点

of_find_node_by_name()函数

(1)根据名字查找节点

/* 作用 : 查找设备树中特定节点的函数
 * 传入参数 : 
     * from : 指向一个起始节点,该函数从这个节点开始向上遍历设备树,查找匹配名称的节点。
     * name : 要查找的节点的名称
 * 返回值 : 如果找到了匹配名称的节点,函数将返回该节点的指针(struct device_node*)。否则返回NULL。
*/
struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
/*------- 示例 --------*/
/* NULL : 表示从根节点开始查找
 * name : 节点的compatible名字为led_test_compatible
*/
of_find_node_by_name(NULL,"led_test_compatible");


of_find_node_by_path()函数

(1)这个是根据路径查找对应节点

/* 作用 : 根据节点的路径查找节点
 * 传入参数 : 
     * path : 一个以斜杠(/)分隔的字符串,表示设备树中从根节点到目标节点的路径
 * 返回值 : 如果找到了匹配名称的节点,函数将返回该节点的指针(struct device_node*)。否则返回NULL。
*/
of_find_node_by_path(const char *path);
/*------- 示例 --------*/
/* "/topeet/myled" : 在根节点下的topeet节点中,找到myled节点
*/
of_find_node_by_path("/topeet/myled");


of_get_parent()函数

(1)获取指定节点的父节点

/* 作用 : 获取指定节点的父节点
 * 传入参数 : 
     * node : 要获得哪个节点的父节点
 * 返回值 : 成功返回查找到的父节点,失败返回NULL
*/
struct device_node *of_get_parent(const struct device_node *node);
/*------- 示例 --------*/
struct device_node * mydevice_node;
/* "/topeet/myled" : 在根节点下的topeet节点中,找到myled节点
*/
mydevice_node = of_find_node_by_path("/topeet/myled");
printk("mydevice node is %s n",mydevice_node->name); //打印出myled
/* mydevice_node : 获得mydevice_node的父节点
*/
mydevice_node = of_get_parent(mydevice_node);
printk("mydevice bab node is %s n",mydevice_node->name); //打印出topeet

of_get_next_child()函数

(1)获取指定节点的父节点

/* 作用 : 获取指定节点的父节点
 * 传入参数 : 
     * node : 父节点
     * prev : 从哪个子节点开始查找下一个子节点,设置为NULL,表示从第一个节点开始
 * 返回值 : 成功返回查找到的节点,失败返回NULL
*/
struct device_node *of_get_next_child(const struct device_node *node,
  struct device_node *prev)
/*------- 示例 --------*/
struct device_node * mydevice_node;
/* "/topeet/myled" : 在根节点下的topeet节点中,找到myled节点
*/
mydevice_node = of_find_node_by_path("/topeet/myled");
printk("mydevice node is %s n",mydevice_node->name); //打印出myled
/* mydevice_node : 获得mydevice_node的父节点
*/
mydevice_node = of_get_parent(mydevice_node);
printk("mydevice bab node is %s n",mydevice_node->name); //打印出topeet
/* mydevice_node : 获得mydevice_node的子节点
 * NULL : 从第一个节点开始
*/
mydevice_node = of_get_next_child(mydevice_node,NULL);
printk("mydevice child node is %s n",mydevice_node->name); //打印出myled

of_find_compatible_node()函数

(1)通过device_type和compatible属性来查找指定节点

/* 作用 : 通过device_type和compatible属性来查找指定节点
 * 传入参数 : 
     * from : 从哪个节点开始查找,如果是NULL表示从根节点开始查找
     * type : 要查找的节点的device_type属性的属性值,可以为NULL,代表忽略device_type属性
     * compatible: 要查找的节点对应的compatible属性列表
 * 返回值 : 成功返回查找到的节点,失败返回NULL
*/
of_find_compatible_node(struct device_node *from, const char *type, const char *compat);
/*------- 示例 --------*/
struct device_node * mydevice_node;
/* NULL : 从根节点开始查找
 * NULL : 表示要查找的节点没有device_type属性,或者是忽略查找他的device_type属性
 * "my_devicetree" : 要查找的节点compatible属性叫做my_devicetree
*/
mydevice_node = of_find_compatible_node(NULL,NULL,"my_devicetree");

使用OF函数获得属性

of_find_property()函数

(1)查找节点下指定的属性

/* 作用 : 查找节点下指定的属性
 * 传入参数 : 
     * np : 查找的节点
     * name : 要查找的属性的属性名
     * lenp : 属性值的字节数
 * 返回值 :  成功返回查找的属性,失败返回NULL
*/
struct property *of_find_property(const struct device_node *np,
          const char *name,
          int *lenp)
/*------- 示例 --------*/
struct device_node * mydevice_node;
struct property *my_property;
/* NULL : 表示从根节点开始查找
 * name : 节点的compatible名字为led_test_compatible
*/
mydevice_node = of_find_node_by_name(NULL,"led_test_compatible");
my_property = of_find_property(mydevice_node,"compatible",&size);


of_property_count_elems_of_size函数

(1)获取属性中元素的数量

/* 作用 : 获取属性中元素的数量
 * 传入参数 : 
     * np : 设备节点
     * propname : 需要获取那个属性的元素数量
     * elem_size : 单个元素尺寸
 * 返回值 :  成功返回元素的数量
*/
int of_property_count_elems_of_size(const struct device_node *np,
        const char *propname, int elem_size)
/*------- 示例 --------*/
struct device_node * mydevice_node;
int num;
/* NULL : 表示从根节点开始查找
 * name : 节点的compatible名字为my_devicetree
*/
mydevice_node = of_find_node_by_name(NULL,"my_devicetree");
/* mydevice_node : 在mydevice_node 设备节点中查找
 * "reg" : 需要获取reg属性的元素数量
 * 4 : 元素是4个字节
*/
num = of_property_count_elems_of_size(mydevice_node,"reg",4);
printk("reg num is %d\n",num); //因为reg中有两个元素,所以打印2


of_property_read_uxx_index()函数

(1)这种类型的函数有2种:of_property_read_u32_index() 函数;of_property_read_u64_index() 函数;

(2)这里分别代表要读取的数据是多少个bit。示例如下

/* 作用 : 获取属性中元素的数量
 * 传入参数 : 
     * np : 设备节点
     * propname : 要读取的属性名字
     * index : 读取这个属性下第几个值,index从0开始
     * out_value : 读到的值
 * 返回值 :  成功返回0
*/
int of_property_read_u32_index(const struct device_node *np,
               const char *propname,
               u32 index, u32 *out_value)
/*------- 示例 --------*/
struct device_node * mydevice_node;
u32 value_u32;
u64 value_u64;
/* NULL : 表示从根节点开始查找
 * name : 节点的compatible名字为my_devicetree
*/
mydevice_node = of_find_node_by_name(NULL,"my_devicetree");
/* mydevice_node : 在mydevice_node 设备节点中查找
 * "reg" : 需要读取的reg属性
 * 0 : 从第0个元素开始读取
 * value_uxx : 读取到的数据存放在这个变量种
*/
of_property_read_u32_index(mydevice_node,"reg",0,&value_u32);
of_property_read_u64_index(mydevice_node,"reg",0,&value_u64);
printk("value u32 is 0x%x\n",value_u32);  
printk("value u64 is 0x%llx\n",value_u64);


of_property_read_variable_uxx_array()函数

(1)这种类型的函数有4种:of_property_read_variable_u8_array() 函数;of_property_read_variable_u16_array() 函数;of_property_read_variable_u32_array() 函数;of_property_read_variable_u64_array() 函数;

(2)这里表示读取的数组,是什么类型的数组。示例如下


/* 作用 : 获取属性中元素的数量
 * 传入参数 : 
     * np : 设备节点
     * propname : 要读取的属性名字
     * out_values : 读取到的数组值,分别为u8、u16、u32、u64
     * sz_min : 数组最小值
     * sz_max : 数组最大值
 * 返回值 :  成功返回0
*/
int of_property_read_variable_u32_array(const struct device_node *np,
             const char *propname, u32 *out_values,
             size_t sz_min, size_t sz_max)
/*------- 示例 --------*/
struct device_node * mydevice_node;
u32 out_value[2];
/* NULL : 表示从根节点开始查找
 * name : 节点的compatible名字为my_devicetree
*/
mydevice_node = of_find_node_by_name(NULL,"my_devicetree");
/* mydevice_node : 在mydevice_node 设备节点中查找
 * "reg" : 需要读取的reg属性
 * out_value : 读取到的数组数据存储在这个里面
 * 1 : 读取的数组最少1个元素
 * 2 : 读取的数组最多2个元素
*/
of_property_read_variable_u32_array(mydevice_node, "reg" ,out_value,1,2);
printk("out value[0] is 0x%x\n",out_value[0]); //打印0xfdd6000
printk("out value[1] is 0x%x\n",out_value[1]); //打印0x4

of_property_read_string()函数

(1)读取属性中的字符串

/* 作用 : 读取属性中的字符串
 * 传入参数 : 
     * np : 设备节点
     * propname : 要读取的属性名字
     * out_string : 读取到的字符串
 * 返回值 :  成功返回0
*/
int of_property_read_string(const struct device_node *np, const char *propname,
        const char **out_string)
/*------- 示例 --------*/
struct device_node * mydevice_node;
const char *value_compatible;
/* NULL : 表示从根节点开始查找
 * name : 节点的compatible名字为my_devicetree
*/
mydevice_node = of_find_node_by_name(NULL,"my_devicetree");
/* mydevice_node : 在mydevice_node 设备节点中查找
 * "compatible" : 需要读取的compatible属性
 * value_compatible : 读取到compatible属性的字符串
*/
of_property_read_string(mydevice_node,"compatible",&value_compatible);
printk("compatible value is %s\n",value_compatible); //打印my_devicetree


总结

设备树其实就是将设备信息从原来存放在c文件中,变成一种树形结构来存储,然后是dts文件存储。这个能够很好的将驱动文件与设备文件剥离。实现真正的脱离硬件。

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
6天前
|
Unix Linux Shell
linux入门!
本文档介绍了Linux系统入门的基础知识,包括操作系统概述、CentOS系统的安装与远程连接、文件操作、目录结构、用户和用户组管理、权限管理、Shell基础、输入输出、压缩打包、文件传输、软件安装、文件查找、进程管理、定时任务和服务管理等内容。重点讲解了常见的命令和操作技巧,帮助初学者快速掌握Linux系统的基本使用方法。
38 3
|
1月前
|
机器学习/深度学习 Linux 编译器
Linux入门3——vim的简单使用
Linux入门3——vim的简单使用
55 1
|
1月前
|
Linux Shell Windows
Linux入门1——初识Linux指令
Linux入门1——初识Linux指令
26 0
Linux入门1——初识Linux指令
|
1月前
|
存储 数据可视化 Linux
Linux 基础入门
Linux 基础入门
|
1月前
|
Linux Go 数据安全/隐私保护
Linux入门2——初识Linux权限
Linux入门2——初识Linux权限
26 0
|
2月前
|
Linux 程序员 编译器
Linux内核驱动程序接口 【ChatGPT】
Linux内核驱动程序接口 【ChatGPT】
下一篇
无影云桌面