Linux驱动之MISC设备驱动
目录
misc 的意思是混合、杂项的,因此 MISC 驱动也叫做杂项驱动,也就是当我们板子上的某些外设无法进行分类的时候就可以使用 MISC 驱动。 MISC 驱动其实就是最简单的字符设备驱动,通常嵌套在 platform 总线驱动中,实现复杂的驱动,本章我们就来学习一下 MISC 驱动的编写。
一、MISC 设备驱动简介
所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。随着 Linux字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主设备号, MISC 设备驱动就用于解决此问题。 MISC 设备会自动创建 cdev,不需要像我们以前那样手动创建,因此采用 MISC 设备驱动可以简化字符设备驱动的编写。我们需要向 Linux 注册一个 miscdevice 设备, miscdevice是一个结构体,定义在文件 include/linux/miscdevice.h 中,内容如下:
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
定义一个 MISC 设备(miscdevice 类型)以后我们需要设置 minor、 name 和 fops 这三个成员变量。 minor 表示子设备号, MISC 设备的主设备号为 10,这个是固定的,需要用户指定子设备号, Linux 系统已经预定义了一些 MISC 设备的子设备号,这些预定义的子设备号定义在include/linux/miscdevice.h 文件中,如下所示:
#define PSMOUSE_MINOR 1
#define MS_BUSMOUSE_MINOR 2 /* unused */
#define ATIXL_BUSMOUSE_MINOR 3 /* unused */
/*#define AMIGAMOUSE_MINOR 4 FIXME OBSOLETE */
#define ATARIMOUSE_MINOR 5 /* unused */
#define SUN_MOUSE_MINOR 6 /* unused */
#define APOLLO_MOUSE_MINOR 7 /* unused */
#define PC110PAD_MINOR 9 /* unused */
......
#define VHOST_VSOCK_MINOR 241
#define RFKILL_MINOR 242
#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,成功。
以前我们需要自己调用一堆的函数去创建设备,比如在以前的字符设备驱动中我们会使用如下几个函数完成设备创建过程:
/* 传统的创建设备过程 */
alloc_chrdev_region(); /* 申请设备号 */
cdev_init(); /* 初始化 cdev */
cdev_add(); /* 添加 cdev */
class_create(); /* 创建类 */
device_create(); /* 创建设备 */
现在我们可以直接使用 misc_register 一个函数来完成上面的传统的创建设备过程中的这些步骤。 当我们卸载设备驱动模块的时候需要调用 misc_deregister 函数来注销掉 MISC 设备,函数原型如下:
int misc_deregister(struct miscdevice *misc)
函数参数和返回值含义如下:
misc:要注销的 MISC 设备。
返回值: 负数,失败; 0,成功。
以前注销设备驱动的时候,我们需要调用一堆的函数去删除此前创建的 cdev、设备等等内容,如下所示:
/* 传统的删除设备的过程 */
cdev_del(); /* 删除 cdev */
unregister_chrdev_region(); /* 注销设备号 */
device_destroy(); /* 删除设备 */
class_destroy(); /* 删除类 */
现在我们只需要一个 misc_deregister 函数即可完成传统的删除设备的过程中的这些工作。关于MISC 设备驱动就讲解到这里,接下来我们就使用 platform 加 MISC 驱动框架来编写 beep 蜂鸣器驱动。
二、修改设备树文件
1、添加 pinctrl 节点
I.MX6U-ALPHA开发板上的 BEEP使用了 SNVS_TAMPER1这个 PIN,打开 imx6ul-14x14-evk.dtsi ,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_beep”的子节点,节点内容如下所示:
pinctrl_beep: beepgrp {
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0 /* beep */
>;
};
第 3 行 , 将 SNVS_TAMPER1 这 个 PIN 复 用 为 GPIO5_IO01 , 宏MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 定义在 arch/arm/boot/dts/imx6ul-pinfunc.h文件中。
2、添加 BEEP 设备节点
在根节点“/”下创建 BEEP 节点,节点名为“beep”,节点内容如下:
beep {
#address-cells = <1>;
#size-cells = <1>;
compatible = "imx6ull-beep";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_beep>;
beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
status = "okay";
};
第 6 行, pinctrl-0 属性设置蜂鸣器所使用的 PIN 对应的 pinctrl 节点。 第 7 行, beep-gpio 属性指定了蜂鸣器所使用的 GPIO。
三、驱动编写
设备树准备好以后就可以编写驱动程序了,在 misc_beep.c 里面输入如下内容:
/************************************************************
* Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
* Description:
* Version: 1.0
* Autor:
* Date:
* LastEditors:
* LastEditTime:
************************************************************/
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#define MISC_BEEP_NAME "misc_beep" /* 设备名字 */
#define MISC_BEEP_MINOR 144 /* 子设备号 */
#define BEEP_ON 1 /* 打开蜂鸣器 */
#define BEEP_OFF 0 /* 关闭蜂鸣器 */
/* beep 设备结构体 */
struct misc_beep_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int beep_gpio; /* beep 所使用的GPIO编号*/
};
struct misc_beep_dev misc_beep;
/*
* @Brief beep打开/关闭
* @Call Internal or External
* @Param state: 1-打开 0-关闭
* @Note NOne
* @RetVal 无
*/
void beep_switch(u8 state)
{
if(state == BEEP_ON)
{
gpio_set_value(misc_beep.beep_gpio, 0);
}
else if(state == BEEP_OFF)
{
gpio_set_value(misc_beep.beep_gpio, 1);
}
else
{
printk("%s state:%d invalid\n", __func__, state);
}
}
/*
* @Brief 打开设备
* @Call Internal or External
* @Param inode:
* @Param filp:设备文件
* @Note NOne
* @RetVal 0:成功 其他值:失败
*/
static int misc_beep_open(struct inode *inode, struct file *filp)
{
/* 设置私有数据 */
filp->private_data = &misc_beep;
return 0;
}
/*
* @Brief 写数据到设备
* @Call Internal or External
* @Param filp:要打开的设备文件描述符
* @Param buf:要写入设备的数据地址
* @Param cnt:要写入的数据长度
* @Param offt:相对于文件首地址的偏移
* @Note NOne
* @RetVal 写入的字节数,若为负值,表示写失败
*/
static ssize_t misc_beep_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retval;
unsigned char databuf[1];
unsigned char beepstat;
retval = copy_from_user(databuf, buf, cnt);
if(retval < 0)
{
printk("%s copy_from_user failed\n", __func__);
return -EFAULT;
}
beepstat = databuf[0];
if(beepstat != BEEP_ON && beepstat != BEEP_OFF)
{
printk("%s beepstat:%d invalid\n", __func__, beepstat);
return -1;
}
/*打开、关闭LED*/
beep_switch(beepstat);
return 0;
}
/* 设备操作函数 */
static struct file_operations misc_beep_fops = {
.owner = THIS_MODULE,
.open = misc_beep_open,
.write = misc_beep_write,
};
/* MISC 设备结构体 */
static struct miscdevice beep_miscdev = {
.minor = MISC_BEEP_MINOR,
.name = MISC_BEEP_NAME,
.fops = &misc_beep_fops,
};
/*
* @Brief platform 驱动probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @Call Internal or External
* @Param dev:platform设备
* @Note NOne
* @RetVal NOne
*/
static int misc_beep_probe(struct platform_device *dev)
{
int ret;
/* 设置beep 所使用的GPIO*/
/* 1.获取设备节点:beep */
misc_beep.nd = of_find_node_by_path("/beep");
if(misc_beep.nd == NULL)
{
printk("beep node can not found\n");
return -EINVAL;
}
/* 2.获取 设备树中的 gpio 属性,得到 beep 所使用的 gpio 编号 */
misc_beep.beep_gpio = of_get_named_gpio(misc_beep.nd, "beep-gpio", 0);
if(misc_beep.beep_gpio < 0)
{
printk("can't get beep-gpio\n");
return -EINVAL;
}
/* 3.设置 GPIO5_IO01 为输出,并且输出到电平,默认关闭 beep */
ret = gpio_direction_output(misc_beep.beep_gpio, 1);
if(ret < 0)
{
printk("can't set gpio\n");
return -EINVAL;
}
/* 注册MISC设备驱动 */
ret = misc_register(&beep_miscdev);
if (ret < 0) {
printk("misc device register failed\n");
return -EFAULT;
}
return 0;
}
/*
* @Brief 驱动出口函数
* @Call Internal or External
* @Param None
* @Note NOne
* @RetVal NOne
*/
static int misc_beep_remove(struct platform_device *dev)
{
/* 关闭蜂鸣器 */
gpio_set_value(misc_beep.beep_gpio, 1);
/* 释放gpio */
gpio_free(misc_beep.beep_gpio);
/* 注销 misc 设备驱动 */
misc_deregister(&beep_miscdev);
return 0;
}
/* 匹配列表 */
static const struct of_device_id beep_of_match[] = {
{ .compatible = "imx6ull-beep" },
{ /* Sentinel*/ }
};
/* platform 驱动结构体 */
static struct platform_driver beep_driver = {
.driver = {
.name = "imx6ull-beep", /* 驱动名字 */
.of_match_table = beep_of_match,/* 设备树匹配列表 */
},
.probe = misc_beep_probe,
.remove = misc_beep_remove,
};
/*
* @Brief 驱动入口函数
* @Call Internal or External
* @Param None
* @Note NOne
* @RetVal NOne
*/
static int __init misc_beep_init(void)
{
return platform_driver_register(&beep_driver);
}
/*
* @Brief 驱动出口函数
* @Call Internal or External
* @Param None
* @Note NOne
* @RetVal NOne
*/
static void __exit misc_beep_exit(void)
{
platform_driver_unregister(&beep_driver);
}
module_init(misc_beep_init);
module_exit(misc_beep_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");
四、编写测试 APP
新建 misc_beep_app.c 文件,然后在里面输入如下所示内容:
/********************************************
*Description:
*Version: 1.0
*Autor:
*Date:
*LastEditors:
*LastEditTime:
********************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#define BEEPON 1
#define BEEPOFF 0
/*
* @Brief main 主程序
* @Call Internal or External
* @Param argc:
* @Param argv:
* @Note NOne
* @RetVal 0-成功;其他-失败
*/
int main(int argc, char *argv[])
{
int fd, retval;
char *filename;
unsigned char databuf[1];
if(argc != 3)
{
printf("argc != 3\n");
return -1;
}
filename = argv[1];
/*打开驱动文件*/
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("open filename:%d failed\n", filename);
return -1;
}
/* 要执行的操作:打开或关闭 */
databuf[0] = atoi(argv[2]);
retval = write(fd, databuf, sizeof(databuf));
if(retval < 0)
{
printf("write file:%s failed\n", filename);
}
/*关闭文件*/
close(fd);
return 0;
}
五、运行测试
将编译出来 misc_beep.ko 和 misc_beep_app 这两个文件拷贝到 /lib/modules/5.19.0-g794a2f7be62d-dirty/ 目录中,重启开发板,进入到目录 /lib/modules/5.19.0-g794a2f7be62d-dirty/ 中,输入如下命令加载 miscbeep.ko 这个驱动模块。
cd /lib/modules/5.19.0-g794a2f7be62d-dirty
insmod misc_beep.ko
当驱动模块加载成功以后我们可以在/sys/class/misc 这个目录下看到一个名为“misc_beep”的子目录,如下所示:
/ # ls /sys/class/misc/
autofs hw_random rfkill watchdog
cpu_dma_latency loop-control ubi_ctrl
fuse misc_beep vga_arbiter
所有的 misc 设备都属于同一个类, /sys/class/misc 目录下就是 misc 这个类的所有设备,每个设备对应一个子目录。
驱动与设备匹配成功以后就会生成/dev/misc_beep 这个设备驱动文件,输入如下命令查看这个文件的主次设备号:
ls -l /dev/misc_beep
结果如下所示:
/ # ls /dev/misc_beep -al
crw------- 1 0 0 10, 144 Jan 5 18:43 /dev/misc_beep
从上面可以看出, /dev/misc_beep 这个设备的主设备号为 10,次设备号为 144,和我们驱动程序里面设置的一致。 输入如下命令打开 BEEP:
./misc_beep_app /dev/misc_beep 1
输入如下命令关闭 BEEP:
./misc_beep_app /dev/misc_beep 0
关于更多嵌入式C语言、FreeRTOS、RT-Thread、Linux应用编程、linux驱动等相关知识,关注公众号【嵌入式Linux知识共享】,后续精彩内容及时收看了解。