前言
misc 的意思是混合、杂项的,因此 MISC 驱动也叫做杂项驱动。也就是当我们板子上的某些外设无法进行分类的时候就可以使用 MISC 驱动。 MISC 驱动其实就是最简单的字符设备驱动,通常嵌套在 platform 总线驱动中,实现复杂的驱动,接下来就来讲下一下MISC的使用,其实总结就是一句话,用MISC 设备驱动来简化字符设备驱动的编写,也就是替代我们之前注册字符设备的那一堆操作。
一.MISC设备驱动简介
所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。随着 Linux字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主设备号, MISC 设备驱动就用于解决此问题。
MISC 设备会自动创建 cdev,不需要像我们以前那样手动创建,因此采用 MISC 设备驱动可以简化字符设备驱动的编写。
1.miscdevice 设备结构体:
同样的,我们要使用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;
};
这个结构体需要我们填入的参数有:minor、 name 和 fops 这三个成员变量。
<1>minor:
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
我们在使用的时候可以从这些预定义的子设备号中挑选一个,当然也可以自己定义,只要这个子设备号没有被其他设备使用接口。
<2>name :
name 就是此 MISC 设备名字,当此设备注册成功以后就会在/dev 目录下生成一个名为 name的设备文件。
<3>fops :
fops 是字符设备的操作函数集合, MISC 设备驱动最终是需要使用用户提供的 fops操作集合。也就是之前我们自己手动注册字符设备的操作函数:
/* 设备操作函数结构体 */
static const struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = gpioled_open,
.read = gpioled_read,
.write = gpioled_write,
.release = gpioled_release
};
2.miscdevice 注册函数:
函数原型如下:
int misc_register(struct miscdevice * misc)
函数参数和返回值含义如下:
misc:要注册的 MISC 设备。
返回值: 负数,失败; 0,成功。
以前我们需要自己调用一堆的函数去创建设备,比如在以前的字符设备驱动中我们会使用如下几个函数完成设备创建过程:
/* 传统的创建设备过程 */
alloc_chrdev_region(); /* 申请设备号 */
cdev_init(); /* 初始化 cdev */
cdev_add(); /* 添加 cdev */
class_create(); /* 创建类 */
device_create(); /* 创建设备 */
现在我们可以直接使用 misc_register 一个函数来完成上面的传统的创建设备过程中的这些步骤。如下所示:
ret = misc_register(&led_miscdev);
if(ret < 0){
printk("misc device register failed!\r\n");
return -EFAULT;
}
3.misc_deregister卸载函数:
当我们卸载设备驱动模块的时候需要调用 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 驱动框架来编写 led驱动。如下所示:
misc_deregister(&led_miscdev);
二、设备树的修改:
在设备树下的根节点添加以下结点:
gpioled{
#address-cells = <1>;
#size-cells = <1>;
compatible = "led-gpio";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
state = "okay";
};
在iomux节点下添加;
pinctrl_led:ledgrp{
fsl,pin=<
/* 配置 GPIO1_IO03 的 IO 属性
*bit 16:0 HYS 关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper 功能
*bit [12]: 1 pull/keeper 使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度 100Mhz
*bit [5:3]: 110 R0/6 驱动能力
*bit [0]: 0 低转换率
*/
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
三、驱动代码的编写
1.引入框架
框架就使用我们之前的platfrom-led的框架不过在peobe函数里面,我们要把关于那些字符设备注册初始化的部分删除了:
static int led_probe(struct platform_device *dev)
{
myled_init(&gpioled);
return 0;
}
剩下gpio初始化部分就可以了。
2.创建miscdevice结构体
#define DEVICE_NAME "miscled"
#define DEVICE_MINOR 145 /* 子设备号 */
/* MISC设备结构体 */
static struct miscdevice led_miscdev = {
.minor = DEVICE_MINOR,
.name = DEVICE_NAME,
.fops = &gpioled_fops,
};
3.使用.miscdevice 注册函数
/*当谁列表的设备和驱动匹配上后执行的peobe函数*/
static int led_probe(struct platform_device *dev)
{
int ret = 0;
myled_init(&gpioled);
ret = misc_register(&led_miscdev);
if(ret < 0){
printk("misc device register failed!\r\n");
return -EFAULT;
}
return 0;
}
4.misc_deregister卸载函数
static int led_remove(struct platform_device *dev)
{
gpio_set_value(gpioled.led_gpio,1);
gpio_free(gpioled.led_gpio);
misc_deregister(&led_miscdev);
printk("gpioled exit!\r\n");
return 0;
}
完整代码:
/**************头文件区域*********************************************************/
#include <linux/ide.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/fcntl.h>
#include <linux/miscdevice.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <linux/io.h>
/**********************************************************************************/
/************************函数定义-begin***********************************************/
static int gpioled_release(struct inode *inode, struct file *file);
static ssize_t gpioled_read(struct file *file, char __user *buf, size_t size, loff_t *ptr);
static ssize_t gpioled_write(struct file *file, const char __user *buf, size_t size, loff_t *ptr);
static int gpioled_open(struct inode *inode , struct file *file);
static int led_probe(struct platform_device *dev);
static int led_remove(struct platform_device *dev);
/************************函数定义-end********************************************/
/************************宏定义-begin***********************************************/
#define DEVICE_NAME "miscled"
#define DEVICE_MINOR 145 /* 子设备号 */
#define DEVICE_CNT 1
#define BEEP_ON 1
#define BEEP_OFF 0
/************************宏定义-end********************************************/
/************************结构体定义-begin***********************************************/
/* dtsled设备信息结构体 */
struct dts_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* led 所使用的 GPIO 编号 */
};
struct dts_dev gpioled; /* led设备 */
/* 设备操作函数结构体 */
static const struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = gpioled_open,
.read = gpioled_read,
.write = gpioled_write,
.release = gpioled_release
};
/* MISC设备结构体 */
static struct miscdevice led_miscdev = {
.minor = DEVICE_MINOR,
.name = DEVICE_NAME,
.fops = &gpioled_fops,
};
/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
{ .compatible = "led-gpio" },
{ /* Sentinel */ }
};
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led",
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};
/************************结构体定义-end***********************************************/
/************************file_operations操作函数-begin***********************************************/
static int gpioled_release(struct inode *inode, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static ssize_t gpioled_read(struct file *file, char __user *buf, size_t size, loff_t *ptr)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static ssize_t gpioled_write(struct file *file, const char __user *buf, size_t size, loff_t *ptr)
{
int ret;
unsigned char databuf[1];
unsigned char ledstate;
struct dts_dev *dev = file->private_data;
ret = __copy_from_user(databuf,buf,size);
if(ret < 0)
{
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstate = databuf[0];
if(ledstate == BEEP_OFF){
gpio_set_value(dev->led_gpio,1);
}
else if(ledstate == BEEP_ON){
gpio_set_value(dev->led_gpio,0);
}
return 0;
}
static int gpioled_open(struct inode *inode , struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
file->private_data = &gpioled; /* 设置私有数据 */
return 0;
}
/************************file_operations操作函数-end***********************************************/
/*****************led初始化函数************************/
static int myled_init(struct dts_dev *dev)
{
int ret = 0;
/* 1、设置 led 所使用的 GPIO */
dev->nd = of_find_node_by_path("/gpioled");
if(dev->nd == NULL){
printk("gpioled node cant not found!\r\n");
return -EINVAL;
}
else{
printk("gpioled node hase been found!\r\n");
}
/* 2、 获取设备树中的 gpio 属性,得到 led 所使用的 led 编号 */
dev->led_gpio = of_get_named_gpio(dev->nd,"gpios",0);
if(dev->led_gpio < 0)
{
printk("can't get led-gpio\r\n");
return -EINVAL;
}
printk("led-gpio num = %d\r\n", dev->led_gpio);
/* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 led 灯 */
ret = gpio_request(dev->led_gpio,"led");
if(ret < 0){
printk("led-gpio request fail\r\n");
return -EINVAL;
}
ret = gpio_direction_output(dev->led_gpio,1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
return ret;
}
/************************platfrom操作函数-begin***********************************************/
/*当谁列表的设备和驱动匹配上后执行的peobe函数*/
static int led_probe(struct platform_device *dev)
{
int ret = 0;
myled_init(&gpioled);
ret = misc_register(&led_miscdev);
if(ret < 0){
printk("misc device register failed!\r\n");
return -EFAULT;
}
return 0;
}
static int led_remove(struct platform_device *dev)
{
gpio_set_value(gpioled.led_gpio,1);
gpio_free(gpioled.led_gpio);
misc_deregister(&led_miscdev);
printk("gpioled exit!\r\n");
return 0;
}
/************************platfrom操作函数-endn***********************************************/
static int __init gpioled_init(void)
{
return platform_driver_register(&led_driver);
}
static void __exit gpioled_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("oudafa");
四、编写测试 APP
这里的测试APP和之前得没什么区别,不用改:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开led驱动 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
/* 向/dev/led文件写入数据 */
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
retvalue = close(fd); /* 关闭文件 */
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
五、运行测试
1.编写makefile
并且使用make命令得到.ko文件和APP文件:
KERN_DIR = /home/odf/linux-imx/linux-imx
all:
clear
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o miscledApp miscledApp.c
clean:
clear
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f miscledApp
obj-m += miscled.o
2.加载模块
将编译出来 .ko文件 和 app文件 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中目录中,重启开发板,进入到目录 rootfs/lib/modules/4.1.15 目录中中,输入如下命令加载 驱动模块:
insmod miscled.ko
所有的 misc 设备都属于同一个类, /sys/class/misc 目录下就是 misc 这个类的所有设备,每个设备对应一个子目录。
驱动与设备匹配成功以后就会生成/dev/miscled这个设备驱动文件,输入如下命令查看这个文件的主次设备号:
ls -l /sys/class/misc
结果如下所示:
从上面可以看出, /dev/misc_beep 这个设备的主设备号为 10,次设备号为 144,和我们驱动程序里面设置的一致。
3.测试代码
输入如下命令打开 led:
./misc_beep_app /dev/miscled 1
输入如下命令关闭 led:
./misc_beep_app /dev/miscled 0