|
1)实验平台:正点原子领航者V2 ZYNQ开发板
2) 章节摘自【正点原子】《领航者ZYNQ之嵌入式Linux开发指南_V2.0》
3)购买链接:https://detail.tmall.com/item.htm?id=609032204975
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-329957-1-1.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:90562473
第四十二章 Linux misc设备驱动实验
misc的意思是混合、杂项的,因此misc设备也叫做杂项设备(杂散设备),为什么会出现这样的设备?Linux系统中大多数设备都有自己归属的类型,例如按键、触摸屏属于输入设备,Linux系统有input子系统框架专门处理这类设备,同样的对于LED设备,有LED驱动框架专门处理LED设备;但是对于adc、蜂鸣器等设备,无法明确其属于什么类型,所以一般把这类设备归属为misc设备,对于misc设备一般推荐使用misc驱动框架编写驱动程序。
本章我们就来学习一下misc驱动框架以及如何使用misc驱动框架编写一个蜂鸣器驱动程序。
1.1MISC设备驱动简介
所有的MISC设备驱动的主设备号都为10,不同的设备使用不同的从设备号。随着Linux字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主设备号,MISC设备驱动就用于解决此问题。MISC设备会自动创建cdev,不需要像我们以前那样手动创建,因此采用MISC设备驱动可以简化字符设备驱动的编写。我们需要向Linux注册一个miscdevice设备,miscdevice是一个结构体,定义在文件include/linux/miscdevice.h中,内容如下:
- 示例代码 42.1.1 miscdevice结构体内容
- 064 struct miscdevice {
- 065 int minor; // 次设备号
- 066 const char *name; // 设备名字
- 067 const struct file_operations *fops; // 设备操作函数集合
- 068 struct list_head list;
- 069 struct device *parent;
- 070 struct device *this_device;
- 071 const struct attribute_group **groups;
- 072 const char *nodename;
- 073 umode_t mode;
- 074 };
复制代码 定义一个MISC设备(miscdevice类型)以后我们需要设置minor、name和fops这三个成员变量。minor表示子设备号,MISC设备的主设备号为10,这个是固定的,需要用户指定子设备号,Linux系统已经预定义了一些MISC设备的子设备号,这些预定义的子设备号定义在include/linux/miscdevice.h文件中,如下所示:
- 示例代码 42.1.2 预定义的misc设备子设备号
- 015 #define PSMOUSE_MINOR 1
- 016 #define MS_BUSMOUSE_MINOR 2 /* unused */
- 017 #define ATIXL_BUSMOUSE_MINOR 3 /* unused */
- 018 /*#define AMIGAMOUSE_MINOR 4 FIXME OBSOLETE */
- 019 #define ATARIMOUSE_MINOR 5 /* unused */
- 020 #define SUN_MOUSE_MINOR 6 /* unused */
- 021 #define APOLLO_MOUSE_MINOR 7 /* unused */
- 022 #define PC110PAD_MINOR 9 /* unused */
- ......
- 059 #define MISC_DYNAMIC_MINOR 255
复制代码 我们在使用的时候可以从这些预定义的子设备号中挑选一个,当然也可以自己定义,只要这个子设备号没有被其他设备使用接口。
name就是此MISC设备名字,当此设备注册成功以后就会在/dev目录下生成一个名为name的设备文件。fops就是字符设备的操作集合,MISC设备驱动最终是需要使用用户提供的fops操作集合。
当设置好miscdevice以后就需要使用misc_register函数向系统中注册一个MISC设备,此函数原型如下:
- int misc_register(struct miscdevice * misc)
复制代码 函数参数和返回值含义如下:
misc:要注册的MISC设备。
返回值:负数,失败;0,成功。
以前我们需要自己调用一堆的函数去创建设备,比如在以前的字符设备驱动中我们会使用如下几个函数完成设备创建过程:
- 示例代码 42.1.3 传统的创建设备过程
- 1 alloc_chrdev_region(); /* 申请设备号 */
- 2 cdev_init(); /* 初始化cdev */
- 3 cdev_add(); /* 添加cdev */
- 4 class_create(); /* 创建类 */
- 5 device_create(); /* 创建设备 */
复制代码 现在我们可以直接使用misc_register一个函数来完成示例代码 42.1.3中的这些步骤。当我们卸载设备驱动模块的时候需要调用misc_deregister函数来注销掉MISC设备,函数原型如下:
- int misc_deregister(struct miscdevice *misc)
复制代码 函数参数和返回值含义如下:
misc:要注销的MISC设备。
返回值:负数,失败;0,成功。
以前注销设备驱动的时候,我们需要调用一堆的函数去删除此前创建的cdev、设备等等内容,如下所示:
- 示例代码 42.1.4 传统的删除设备的过程
- 1 cdev_del(); /* 删除cdev */
- 2 unregister_chrdev_region(); /* 注销设备号 */
- 3 device_destroy(); /* 删除设备 */
- 4 class_destroy(); /* 删除类 */
复制代码 现在我们只需要一个misc_deregister函数即可完成示例代码 42.1.4中的这些工作。关于MISC设备驱动就讲解到这里,接下来我们就使用platform加MISC驱动框架来编写beep蜂鸣器驱动。
1.2硬件原理图分析
本章实验我们只使用到开发板上的BEEP蜂鸣器,因此实验硬件原理图参考28.2小节即可。
1.3实验程序编写
本实验对应的例程路径为:领航者ZYNQ开发板光盘资料(A盘)\4_SourceCode\3_Embedded_Linux\Linux驱动例程\20_miscbeep。
本章实验我们采用platform总线加misc驱动框架的方式编写beep驱动,这也是实际的Linux驱动中很常用的方法。采用platform来实现总线、设备和驱动,调用misc设备驱动框架提供的接口完成misc类字符设备的创建。
1.3.1修改设备树
本章实验我们需要用到蜂鸣器,因此需要在设备树文件system-top.dts中创建蜂鸣器设备节点,这里我们直接使用28.3.1小节创建的beeper这个设备节点即可。
1.3.2beeper驱动程序编写
在drivers目录下新建名为“20_miscbeep”的文件夹作为本驱动实验目录,在20_miscbeep目录下新建名为miscbeep.c的驱动文件,在miscbeep.c中输入如下所示内容:
- 示例代码 42.3.1 miscbeep.c文件代码段
- 1 /***************************************************************
- 2 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
- 3 文件名 : miscbeep.c
- 4 作者 : 邓涛
- 5 版本 : V1.0
- 6 描述 : misc设备驱动框架编程示例之蜂鸣器驱动
- 7 其他 : 无
- 8 论坛 : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- 9 日志 : 初版V1.0 2019/1/30 邓涛创建
- 10 ***************************************************************/
- 11
- 12 #include <linux/module.h>
- 13 #include <linux/of_gpio.h>
- 14 #include <linux/platform_device.h>
- 15 #include <linux/miscdevice.h>
- 16 #include <linux/uaccess.h>
- 17
- 18
- 19 /* 蜂鸣器设备结构体 */
- 20 struct mybeep_dev {
- 21 struct miscdevice mdev; // misc设备
- 22 int gpio; // gpio编号
- 23 };
- 24
- 25 struct mybeep_dev beep_dev; // 定义一个蜂鸣器设备
- 26
- 27 /*
- 28 * @description : beep相关初始化操作
- 29 * @param – pdev : struct platform_device指针,也就是platform设备指针
- 30 * @return : 成功返回0,失败返回负数
- 31 */
- 32 static int mybeep_init(struct platform_device *pdev)
- 33 {
- 34 struct device *dev = &pdev->dev;
- 35 int ret;
- 36
- 37 /* 从设备树中获取GPIO */
- 38 beep_dev.gpio = of_get_named_gpio(dev->of_node, "beeper-gpio", 0);
- 39 if(!gpio_is_valid(beep_dev.gpio)) {
- 40 dev_err(dev, "Failed to get gpio");
- 41 return -EINVAL;
- 42 }
- 43
- 44 /* 申请使用GPIO */
- 45 ret = devm_gpio_request(dev, beep_dev.gpio, "Beep Gpio");
- 46 if (ret) {
- 47 dev_err(dev, "Failed to request gpio");
- 48 return ret;
- 49 }
- 50
- 51 /* 将GPIO设置为输出模式并将输出低电平 */
- 52 gpio_direction_output(beep_dev.gpio, 0);
- 53
- 54 return 0;
- 55 }
- 56
- 57 /*
- 58 * @description : 打开设备
- 59 * @param – inode : 传递给驱动的inode
- 60 * @param – filp : 设备文件,file结构体有个叫做private_data的成员变量
- 61 * 一般在open的时候将private_data指向设备结构体。
- 62 * @return : 0 成功;其他 失败
- 63 */
- 64 static int mybeep_open(struct inode *inode, struct file *filp)
- 65 {
- 66 return 0;
- 67 }
- 68
- 69 /*
- 70 * @description : 向设备写数据
- 71 * @param – filp : 设备文件,表示打开的文件描述符
- 72 * @param – buf : 要写给设备写入的数据
- 73 * @param – cnt : 要写入的数据长度
- 74 * @param – offt : 相对于文件首地址的偏移
- 75 * @return : 写入的字节数,如果为负值,表示写入失败
- 76 */
- 77 static ssize_t mybeep_write(struct file *filp, const char __user *buf,
- 78 size_t cnt, loff_t *offt)
- 79 {
- 80 int ret;
- 81 char kern_buf[5];
- 82
- 83 ret = copy_from_user(kern_buf, buf, cnt); // 得到应用层传递过来的数据
- 84 if(0 > ret) {
- 85 printk(KERN_ERR "mybeep: Failed to copy data from user buffer\r\n");
- 86 return -EFAULT;
- 87 }
- 88
- 89 if (0 == kern_buf[0])
- 90 gpio_set_value(beep_dev.gpio, 0); // 如果传递过来的数据是0则关闭beep
- 91 else if (1 == kern_buf[0])
- 92 gpio_set_value(beep_dev.gpio, 1); // 如果传递过来的数据是1则打开beep
- 93
- 94 return 0;
- 95 }
- 96
- 97 /* 蜂鸣器设备操作函数集 */
- 98 static struct file_operations mybeep_fops = {
- 99 .owner = THIS_MODULE,
- 100 .open = mybeep_open,
- 101 .write = mybeep_write,
- 102 };
- 103
- 104 /*
- 105 * @description : platform驱动的probe函数,当驱动与设备
- 106 * 匹配成功以后此函数就会执行
- 107 * @param – pdev : platform设备指针
- 108 * @return : 0,成功;其他负值,失败
- 109 */
- 110 static int mybeep_probe(struct platform_device *pdev)
- 111 {
- 112 struct miscdevice *mdev;
- 113 int ret;
- 114
- 115 dev_info(&pdev->dev, "BEEP device and driver matched successfully!\n");
- 116
- 117 /* 初始化BEEP */
- 118 ret = mybeep_init(pdev);
- 119 if (ret)
- 120 return ret;
- 121
- 122 /* 初始化beep_data变量 */
- 123 mdev = &beep_dev.mdev;
- 124 mdev->name = "zynq-beep"; // 设备名
- 125 mdev->minor = MISC_DYNAMIC_MINOR; // 动态分配次设备号
- 126 mdev->fops = &mybeep_fops; // 绑定file_operations结构体
- 127
- 128 /* 向linux系统misc驱动框架核心层注册一个beep设备 */
- 129 return misc_register(mdev);
- 130 }
- 131
- 132 /*
- 133 * @description : platform驱动模块卸载时此函数会执行
- 134 * @param – dev : platform设备指针
- 135 * @return : 0,成功;其他负值,失败
- 136 */
- 137 static int mybeep_remove(struct platform_device *pdev)
- 138 {
- 139 /* 注销模块时关闭BEEP */
- 140 gpio_set_value(beep_dev.gpio, 0);
- 141
- 142 /* 注销misc设备驱动 */
- 143 misc_deregister(&beep_dev.mdev);
- 144
- 145 dev_info(&pdev->dev, "BEEP driver has been removed!\n");
- 146 return 0;
- 147 }
- 148
- 149 /* 匹配列表 */
- 150 static const struct of_device_id beep_of_match[] = {
- 151 { .compatible = "alientek,beeper" },
- 152 { /* Sentinel */ }
- 153 };
- 154
- 155 /* platform驱动结构体 */
- 156 static struct platform_driver mybeep_driver = {
- 157 .driver = {
- 158 .name = "zynq-beep", // 驱动名字,用于和设备匹配
- 159 .of_match_table = beep_of_match, // 设备树匹配表,用于和设备树中定义的设备匹配
- 160 },
- 161 .probe = mybeep_probe, // probe函数
- 162 .remove = mybeep_remove, // remove函数
- 163 };
- 164
- 165 module_platform_driver(mybeep_driver);
- 166
- 167 MODULE_AUTHOR("DengTao <<a href="mailto:773904075@qq.com">773904075@qq.com</a>>");
- 168 MODULE_DESCRIPTION("BEEP Driver Based on Misc Driver Framework");
- 169 MODULE_LICENSE("GPL");
复制代码 第20~23行,我们定义了一个struct mybeep_dev结构体,用来描述开发板上的蜂鸣器设备,mybeep_dev结构体中包含了一个miscdevice结构体成员变量以及gpio成员变量,gpio变量用于描述控制蜂鸣器打开、关闭对应的GPIO编号。
第25行,定义一个蜂鸣器设备。
第32~55行,自定义函数mybeep_init,用于初始化蜂鸣器设备;其中包括从设备树获取GPIO编号、使用devm_gpio_request函数申请GPIO以及设置GPIO的初始电平状态;使用devm_gpio_request函数申请GPIO,当设备注销(或者设备注册失败)的时候会自动释放,所以不需要我们手动执行相应的释放函数。
第64~67行,mybeep_open函数;第77~95行,mybeep_write函数,分别对应了蜂鸣器设备的file_operations结构体变量中的open和write方法,当应用层调用open函数和write函数的时候会执行到。在mybeep_write函数中,获取应用层传递过来的数据,如果是0则关闭蜂鸣器,如果是1则打开蜂鸣器。
第110~130行,platform平台驱动的probe函数mybeep_probe,当platform设备与platform驱动匹配成功之后会指定此函数;第118行调用自定义函数mybeep_init对蜂鸣器GPIO进行初始化,第124~126行对miscdevice结构体变量进行填充,设置蜂鸣器设备的名字为“zynq-beep”,这样当系统启动以后就会在/dev/目录下存在一个名为“zynq-beep”的设备文件,第125行设置蜂鸣器设备的次设备号为MISC_DYNAMIC_MINOR,其实这里表示的是让misc设备驱动框架核心层给它动态分配一个没有使用到的次设备号,一般都推荐大家这样做,这样比较安全!第126行,设置设备的操作函数集合,为file_operations类型。最后调用misc_register函数向linux misc驱动框架核心层注册一个misc类设备。
第137~147行,platform平台驱动的remove函数mybeep_remove,当模块注销时会调用此函数;在此函数中调用misc_deregister函数来注销misc设备。
1.3.3编写测试APP
在本实验20_miscbeep目录下新建miscbeepApp.c文件,然后在里面输入如下所示内容:
- 示例代码 42.3.2 miscbeepApp.c文件代码段
- 1 /***************************************************************
- 2 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
- 3 文件名 : miscbeepApp.c
- 4 作者 : 邓涛
- 5 版本 : V1.0
- 6 描述 : misc设备驱动框架之蜂鸣器驱动应用层测试代码
- 7 其他 : 无
- 8 使用方法 : ./miscbeepApp /dev/zynq-beep 0 关闭蜂鸣器
- 9 ./miscbeepApp /dev/zynq-beep 1 打开蜂鸣器
- 10 论坛 : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- 11 日志 : 初版V1.0 2019/1/30 邓涛创建
- 12 ***************************************************************/
- 13
- 14 #include <stdio.h>
- 15 #include <unistd.h>
- 16 #include <sys/types.h>
- 17 #include <sys/stat.h>
- 18 #include <fcntl.h>
- 19 #include <stdlib.h>
- 20 #include <string.h>
- 21
- 22 /*
- 23 * @description : main主程序
- 24 * @param – argc : argv数组元素个数
- 25 * @param – argv : 具体参数
- 26 * @return : 0 成功;其他 失败
- 27 */
- 28 int main(int argc, char *argv[])
- 29 {
- 30 int fd, ret;
- 31 unsigned char buf[1];
- 32
- 33 if(3 != argc) {
- 34 printf("Usage:\n"
- 35 "\t./miscbeepApp /dev/zynq-beep 0 @ close Beep\n"
- 36 "\t./miscbeepApp /dev/zynq-beep 1 @ open Beep\n"
- 37 );
- 38 return -1;
- 39 }
- 40
- 41 /* 打开设备 */
- 42 fd = open(argv[1], O_RDWR);
- 43 if(0 > fd) {
- 44 printf("Error: file %s open failed!\r\n", argv[1]);
- 45 return -1;
- 46 }
- 47
- 48 /* 将字符串转换为int型数据 */
- 49 buf[0] = atoi(argv[2]);
- 50
- 51 /* 向驱动写入数据 */
- 52 ret = write(fd, buf, sizeof(buf));
- 53 if(0 > ret){
- 54 close(fd);
- 55 return -1;
- 56 }
- 57
- 58 /* 关闭设备 */
- 59 close(fd);
- 60 return 0;
- 61 }
复制代码 miscbeepApp.c文件内容和其他例程的测试APP基本一致,很简单,这里就不讲解了。
1.4运行测试
1.4.1编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,将上一章驱动实验19_ledframework目录下的Makefile文件拷贝到本实验目录下,打开Makefile文件,将obj-m变量的值改为“miscbeep.o”,Makefile内容如下所示:
- 示例代码 42.4.1 Makefile文件内容
- 1 KERN_DIR := /home/zynq/linux/kernel/linux-xlnx-xilinx-v2018.3
- 2
- 3 obj-m := miscbeep.o
- 4
- 5 all:
- 6 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modules
- 7
- 8 clean:
- 9 make -C $(KERN_DIR) M=`pwd` clean
复制代码 第3行,设置obj-m变量的值为“miscbeep.o”。
修改完成之后保存退出,输入如下命令编译出驱动模块文件:
编译成功以后就会生成一个名为“miscbeep.ko”的驱动模块文件,如下所示:
图 42.4.1 miscbeep.ko驱动模块文件
2、编译测试APP
输入如下命令编译测试miscbeepApp.c这个测试程序:
- arm-linux-gnueabihf-gcc miscbeepApp.c -o miscbeepApp
复制代码 编译成功以后就会生成miscbeepApp这个应用程序。
1.4.2运行测试
将上一小节编译出来miscbeep.ko和miscbeepApp这两个文件拷贝到开发板根文件系统/lib/modules/4.14.0-xilinx目录中,重启开发板,进入到目录/lib/modules/4.14.0-xilinx中,输入如下命令加载miscbeep.ko这个驱动模块。
- <font size="2">depmod //第一次加载驱动的时候需要运行此命令
- modprobe miscbeep.ko //加载设备模块</font>
复制代码 当驱动模块加载成功以后我们可以在/sys/class/misc这个目录下看到一个名为“zynq-beep”的子目录,如图 42.4.2所示:
图 42.4.2 zynq-beep子目录
所有的misc设备都属于同一个类,/sys/class/misc目录下就是misc这个类的所有设备,每个设备对应一个子目录。
驱动与设备匹配成功以后就会生成/dev/zynq-beep这个设备驱动文件,输入如下命令查看这个文件的主次设备号:
结果如图 42.4.3所示:
图 42.4.3 /dev/zynq-beep设备文件
从图 42.4.3可以看出,/dev/zynq-beep这个设备的主设备号为10,次设备号为58,这个次设备号是由misc设备驱动框架动态分配的,并不是我们手动指定的。
输入如下命令打开BEEP:
- ./miscbeepApp /dev/zynq-beep 1 //打开BEEP
复制代码 在输入如下命令关闭LED灯:
- ./miscbeepApp /dev/zynq-beep 0 //关闭BEEP
复制代码 查看开发板上的蜂鸣器能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如下命令即可:
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……
|