第12章 Linux设备驱动的软件架构思想之设备驱动的分层思想(混杂设备驱动)

12.3.6 misc设备驱动

    Linux驱动倾向于分层设计,各个具体的设备都可以找到它归属的类型,从而套到它相应的架构里面去,并且只需要实现最底层的那一部分。但是,也有部分类似globalmem、globalfifo的字符设备,确实不知道它属于什么类型,一般推荐大家采用miscdevice框架结构。miscdevice本质上也是字符设备,在miscdevice核心层(drivers/char/misc.c)的misc_init()函数中,通过register_chrdev(MISC_MAJOR,"misc",&misc_fops)注册字符设备,具体miscdevice实例调用(drivers/char/misc.c)misc_register()函数又自动完成device_create()、获取动态次设备号的动作。

    miscdevice的主设备号是固定的,MISC_MAJOR定义为10,Linux内核中,大概找到200多处使用miscdevice框架结构的驱动。

    miscdevice结构体的定义如代码清单12.21所示。

linux/miscdevice.h

struct miscdevice  {

        /*minor为MISC_DYNAMIC_MINOR,miscdevice核心层会自动找一个空闲的次设
备号,否则用minor指定的次设备号。
*/

        int minor; 
        const char *name;/*设备的名称*/
        const struct file_operations *fops;
        struct list_head list;
        struct device *parent;
        struct device *this_device;
        const char *nodename;
        umode_t mode;

};

备注:miscdevice结构体内file_operations中的成员函数,实际上是由drivers/char/misc.c中misc驱动核心层的misc_fops成员函数间接调用的,比如misc_open()就会间接调用底层注册的miscdevice的fops->open

static int misc_open(struct inode * inode, struct file * file)
{
int minor = iminor(inode);
struct miscdevice *c;
int err = -ENODEV;
const struct file_operations *new_fops = NULL;

mutex_lock(&misc_mtx);
list_for_each_entry(c, &misc_list, list) {
if (c->minor == minor) {
new_fops = fops_get(c->fops);
break;
}
}

if (!new_fops) {
mutex_unlock(&misc_mtx);
request_module("char-major-%d-%d", MISC_MAJOR, minor);
mutex_lock(&misc_mtx);

list_for_each_entry(c, &misc_list, list) {
if (c->minor == minor) {
new_fops = fops_get(c->fops);
break;
}
}
if (!new_fops)
goto fail;
}

err = 0;
replace_fops(file, new_fops);
if (file->f_op->open) {
file->private_data = c;
err = file->f_op->open(inode,file);
}

fail:
mutex_unlock(&misc_mtx);
return err;

}

miscdevice驱动的注册和注销分别用下面两个API:
linux/miscdevice.h
extern int misc_register(struct miscdevice * misc);

extern int misc_deregister(struct miscdevice *misc);

drivers/char/misc.c

/**
 * misc_register - register a miscellaneous device
 * @misc: device structure
 *
 * Register a miscellaneous device with the kernel. If the minor
 * number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
 * and placed in the minor field of the structure. For other cases
 * the minor number requested is used.
 *
 * The structure passed is linked into the kernel and may not be
 * destroyed until it has been unregistered.
 *
 * A zero is returned on success and a negative errno code for
 * failure.
 */

int misc_register(struct miscdevice * misc)
{
dev_t dev;
int err = 0;

INIT_LIST_HEAD(&misc->list);

mutex_lock(&misc_mtx);

if (misc->minor == MISC_DYNAMIC_MINOR) {
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
if (i >= DYNAMIC_MINORS) {
err = -EBUSY;
goto out;
}
misc->minor = DYNAMIC_MINORS - i - 1;
set_bit(i, misc_minors);
} else {
struct miscdevice *c;
list_for_each_entry(c, &misc_list, list) {
if (c->minor == misc->minor) {
err = -EBUSY;
goto out;
}
}
}

dev = MKDEV(MISC_MAJOR, misc->minor);

misc->this_device = device_create(misc_class, misc->parent, dev,
  misc, "%s", misc->name); /* 创建设备 */
if (IS_ERR(misc->this_device)) {
int i = DYNAMIC_MINORS - misc->minor - 1;
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
err = PTR_ERR(misc->this_device);
goto out;
}

/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
list_add(&misc->list, &misc_list);
 out:
mutex_unlock(&misc_mtx);
return err;
}

EXPORT_SYMBOL(misc_register);

/**
 * misc_deregister - unregister a miscellaneous device
 * @misc: device to unregister
 *
 * Unregister a miscellaneous device that was previously
 * successfully registered with misc_register(). Success
 * is indicated by a zero return, a negative errno code
 * indicates an error.
 */

int misc_deregister(struct miscdevice *misc)
{
int i = DYNAMIC_MINORS - misc->minor - 1;

if (WARN_ON(list_empty(&misc->list)))
return -EINVAL;

mutex_lock(&misc_mtx);
list_del(&misc->list);
device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
mutex_unlock(&misc_mtx);
return 0;
}

EXPORT_SYMBOL(misc_deregister);

备注:

miscdevice驱动的一般结构形如:

static const struct file_operations xxx_fops = {
       .unlocked_ioctl = xxx_ioctl,
       .mmap           = xxx_mmap,
            …
};
static struct miscdevice xxx_dev = {
       .minor  = MISC_DYNAMIC_MINOR, /* 动态分配此设备号 */
       .name   = "xxx",
       .fops   = &xxx_fops
};

static int __init xxx_init(void)
{
       pr_info("ARC Hostlink driver mmap at 0x%p\n", __HOSTLINK__);
       return misc_register(&xxx_dev);

}

linux/miscdevice.h

#define MISC_DYNAMIC_MINOR        255

    结论:在调用misc_register(&xxx_dev)时,该函数内部会自动调用device_create(),而device_create()会以xxx_dev作为drvdata参数。其次,在miscdevice核心层misc_open()函数的帮助下,在file_operations的成员函数中,xxx_dev会自动成为file的private_data(misc_open会完成file->private_data的赋值操作)。

    如果用面向对象的封装思想把一个设备的属性、自旋锁、互斥锁、等待队列、miscdevice等封装在一个结构体里面:

struct xxx_dev {
         unsigned int version;
         unsigned int size;
         spinlock_t lock;
        ...
        struct miscdevice miscdev;
};

    在file_operations的成员函数中,可以通过宏container_of()和file->private_data反推出xxx_dev的实例。

static long xxx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 {
        struct xxx_dev *xxx = container_of(file->private_data, struct xxx_dev, miscdev);
        ...
}

globalfifo驱动基于platform_driver且采用miscdevice框架的结构体实例如下:

1、驱动头文件

#ifndef __GLOBAL_MEM_H
#define __GLOBAL_MEM_H

#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/fs.h>

#define DRIVER_AUTHOR "[email protected]"
#define DRIVER_DESC   "A sample driver" 


#define GLOBALFIFO_SIZE 0x1000
#define MEM_CLEAR 0x1
#define DEV_NAME "global_fifo"


/**
*
* 定义全局内存字符设备的结构体:
* 借用面向对象程序设计中“封装”的思想,体现良好的编程习惯。
*
*/
struct globalfifo_dev {
struct miscdevice miscdev;
unsigned int current_len; // FIFO中有效数据的长度
unsigned char mem[GLOBALFIFO_SIZE];
struct mutex mutex; // 互斥锁
wait_queue_head_t r_wait;// 读等待队列头部
wait_queue_head_t w_wait;// 写等待队列头部
struct fasync_struct *async_queue;// 异步通知
};


#endif /* __GLOBAL_MEM_H */

2、平台设备源代码文件

#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/platform_device.h>

#include "vichip.h"

#define PLAT_DEV_NAME "globalfifo"

struct platform_device *plat_dev = NULL;

static int __init vi_platform_dev_init(void)
{
int ret;

plat_dev = platform_device_alloc(PLAT_DEV_NAME, -1);
if (!plat_dev)
        return -ENOMEM;

ret = platform_device_add(plat_dev);
if (ret) {
printk(KERN_ERR "add platform device fail, ret = [%d]\n", ret);
platform_device_put(plat_dev);
return ret;
}

    printk(KERN_INFO "-----vi platform devcie init ok! ret = [%d]----\n", ret);

    return 0; 
}


static void __exit vi_platform_dev_exit(void)
{
platform_device_unregister(plat_dev);
    printk(KERN_INFO "----vi unregister platform devcie success----\n");
}

module_init(vi_platform_dev_init);
module_exit(vi_platform_dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);

MODULE_DESCRIPTION(DRIVER_DESC);

3、平台驱动源代码文件

#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/platform_device.h> 
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/poll.h>

#include "vichip.h"

/**
*
* 读写函数
* 让设备结构体globalfifo_dev的mem[]数组与用户空间交互数据,
* 并随着访问的字节数变更更新文件读写偏移位置。
*/
// globalfifo设备驱动的读函数,从设备中读取数据
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
int ret;
struct globalfifo_dev *dev = container_of(filp->private_data, 
struct globalfifo_dev, miscdev);

DECLARE_WAITQUEUE(wait, current);// 定义并初始化一个名为wait的等待队列元素
mutex_lock(&dev->mutex); // 获取互斥锁
add_wait_queue(&dev->r_wait, &wait); // 把等待队列元素wait添加到读等待队列头部r_wait指向的双向链表中
while (dev->current_len == 0) { // FIFO为空
if (filp->f_flags & O_NONBLOCK) { // 非阻塞
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);// 改变进程状态TASK_INTERRUPTIBLE,浅度睡眠标记,并没有真正睡眠.
mutex_unlock(&dev->mutex); // 释放互斥锁
schedule();// 让读进程进入睡眠,调度其他进程执行
if (signal_pending(current)) { // 判断是否被信号唤醒
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}

if (count > dev->current_len)
count = dev->current_len;

if (copy_to_user(buf, dev->mem, count)) { // 把内核空间中的数据拷贝到用户空间,该函数可能引起阻塞
ret = -EFAULT;
goto out;
} else {
memcpy(dev->mem, dev->mem + count, dev->current_len - count);
dev->current_len -= count;
printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count, dev->current_len);
wake_up_interruptible(&dev->w_wait);// 唤醒可能阻塞的写进程
ret = count;
}

out:
mutex_unlock(&dev->mutex); // 释放互斥锁
out2:
remove_wait_queue(&dev->w_wait, &wait); // 把等待队列元素wait从读等待队列头部r_wait指向的双向链表中移除
set_current_state(TASK_RUNNING); // 设置进程状态为TASK_RUNNING
return ret;
}


// globalfifo设备驱动的写函数,向设备发送数据
static ssize_t globalfifo_write(struct file *filp, const char __user * buf, size_t count, loff_t * ppos)
{
int ret;
struct globalfifo_dev *dev = container_of(filp->private_data, 
struct globalfifo_dev, miscdev);

DECLARE_WAITQUEUE(wait, current);// 定义并初始化一个名为wait的等待队列元素

mutex_lock(&dev->mutex);// 获取互斥锁
add_wait_queue(&dev->w_wait, &wait);// 把等待队列元素wait添加到写等待队列头部w_wait指向的双向链表中
 
while (dev->current_len == GLOBALFIFO_SIZE) { // FIFO为满
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);// 改变进程状态TASK_INTERRUPTIBLE,浅度睡眠标记,并没有真正睡眠.
mutex_unlock(&dev->mutex);
schedule();// 让写进程进入睡眠,调度其他进程执行
if (signal_pending(current)) {// 判断是否被信号唤醒
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
 
if (count > (GLOBALFIFO_SIZE - dev->current_len))
count = GLOBALFIFO_SIZE - dev->current_len;
 
if (copy_from_user(dev->mem + dev->current_len, buf, count)) { // 把用户空间中的数据写到内核空间,该函数可能引起阻塞
ret = -EFAULT;
goto out;
} else {
dev->current_len += count;
printk(KERN_INFO "written %d bytes(s),current_len:%d\n", count, dev->current_len);
wake_up_interruptible(&dev->r_wait);// 唤醒可能阻塞的读进程
ret = count;
}
 
out:
mutex_unlock(&dev->mutex); // 释放互斥锁
out2:
remove_wait_queue(&dev->w_wait, &wait);// 把等待队列元素wait从写等待队列头部w_wait指向的双向链表中移除
set_current_state(TASK_RUNNING);// 设置进程状态为TASK_RUNNING
return ret;
}


// globalfifo设备驱动的seek函数
// seek函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)和文件尾(SEEK_END,2),假设globalfifo支持从文件开头和当前位置的相对偏移。
// 在定位的时候,应该检查用户请求的合法性,若不合法,函数返回-EINVAL,合法时更新文件的当前位置并返回该位置。
static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig) {
case 0: /* 从文件开头位置 seek */
if (offset < 0) {
ret = -EINVAL;
break;
}
if ((unsigned int)offset > GLOBALFIFO_SIZE) {
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /* 从文件当前位置开始 seek */
if ((filp->f_pos + offset) > GLOBALFIFO_SIZE) {
ret = -EINVAL;
break;
}
if ((filp->f_pos + offset) < 0) {
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
break;
}

return ret;
}


// ioctl函数
// globalfifo设备驱动的ioctl函数接受MEM_CLEAR命令,这个命令会将全局内存的有效数据长度清0,对于设备不支持的命令,ioctl函数应该返回-EINVAL。
static long globalfifo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct globalfifo_dev *dev = container_of(filp->private_data, 
struct globalfifo_dev, miscdev);

switch (cmd) {
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALFIFO_SIZE); // 清空内存
printk(KERN_INFO "globalfifo is set to zero\n");
break;
default:
return -EINVAL;
}

return 0;
}

static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
struct globalfifo_dev *dev = container_of(filp->private_data,
struct globalfifo_dev, miscdev);

return fasync_helper(fd, filp, mode, &dev->async_queue);
}


static int globalfifo_open(struct inode *inode, struct file *filep)

printk(KERN_INFO "globalfifo_open\n");
return 0; /* 成功 */
}


static int globalfifo_release(struct inode *inode, struct file *filep)
{
printk(KERN_INFO "globalfifo_release\n");
globalfifo_fasync(-1, filep, 0);
return 0;
}

static unsigned int globalfifo_poll (struct file *filep, struct poll_table_struct *wait)
{
unsigned int mask = 0;

struct globalfifo_dev *dev = container_of(filep->private_data, 
struct globalfifo_dev, miscdev);

mutex_lock(&dev->mutex);
poll_wait(filep, &dev->r_wait, wait); // 加入读等待队列
    poll_wait(filep, &dev->w_wait, wait); // 加入写等待队列

if (dev->current_len != 0) { // 可读(FIFO非空)
mask |= POLLIN | POLLRDNORM; // 设置位掩码
}


if (dev->current_len != GLOBALFIFO_SIZE) { // 可写(FIFO非满)
mask |= POLLOUT | POLLWRNORM; // 设置位掩码
}

mutex_unlock(&dev->mutex);
return mask;
}

// globalfifo设备驱动的文件操作结构体
static const struct file_operations globalfifo_fops = {
.owner = THIS_MODULE,
.llseek = globalfifo_llseek,
.read = globalfifo_read,
.write = globalfifo_write,
.unlocked_ioctl = globalfifo_ioctl,
.open = globalfifo_open,
.release = globalfifo_release,
.poll = globalfifo_poll,
.fasync = globalfifo_fasync,
};

static int globalfifo_probe(struct platform_device *pdev)  
{  
struct globalfifo_dev *globalfifo_devp = NULL;
        int ret;

globalfifo_devp = devm_kzalloc(&pdev->dev, sizeof(*globalfifo_devp), GFP_KERNEL);
    if (!globalfifo_devp)
        return -ENOMEM;

globalfifo_devp->miscdev.minor = MISC_DYNAMIC_MINOR;
    globalfifo_devp->miscdev.name = DEV_NAME;
    globalfifo_devp->miscdev.fops = &globalfifo_fops;

mutex_init(&globalfifo_devp->mutex);  // 初始化互斥锁
init_waitqueue_head(&globalfifo_devp->r_wait);// 初始化读等待队列头部
init_waitqueue_head(&globalfifo_devp->w_wait);// 初始化写等待队列头部
platform_set_drvdata(pdev, globalfifo_devp); // 设置驱动数据
printk(KERN_INFO "%s driver found device!!\n", __func__);  
ret = misc_register(&globalfifo_devp->miscdev); // 注册混杂设备
if (ret < 0)
        goto err;

    return 0;

err:
    return ret;
}  
  
static int globalfifo_remove(struct platform_device *pdev)  
{  
struct globalfifo_dev *gl = platform_get_drvdata(pdev); // 获取驱动数据
    misc_deregister(&gl->miscdev); // 注销混杂设备
printk(KERN_INFO "%s driver found device unpluged!!\n", __func__);  
    return 0;
}    


static struct platform_driver globalfifo_driver = {
    .driver = {
        .name = "globalfifo",
        .owner = THIS_MODULE,
    },
    .probe = globalfifo_probe,
    .remove = globalfifo_remove,
};


module_platform_driver(globalfifo_driver);
#if 0
/* module_platform_driver() - Helper macro for drivers that don't do
 * anything special in module init/exit.  This eliminates a lot of
 * boilerplate.  Each module may only use this macro once, and
 * calling it replaces module_init() and module_exit()
 * #include <linux/platform_device.h>
 */


#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)

/*
 * module_driver() - Helper macro for drivers that don't do anything
 * special in module init/exit. This eliminates a lot of boilerplate.
 * Each module may only use this macro once, and calling it replaces
 * module_init() and module_exit().
 *
 * @__driver: driver name
 * @__register: register function for this driver type
 * @__unregister: unregister function for this driver type
 * @...: Additional arguments to be passed to __register and __unregister.
 *
 * Use this macro to construct bus specific macros for registering
 * drivers, and do not use it on its own.
 * #include <linux/device.h>
 */
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
        return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
        __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

#endif

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

4、Makefile

CONFIG_MODULE_SIG=n
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:                               
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:                                             
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
else
    obj-m := vi_plat_dev.o vi_plat_dri.o

endif

5、测试文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>  
  
#define DEV_NAME "/dev/global_fifo"
#define FIFO_CLEAR 0x1
#define BUFFER_LEN 20

void main(void)  
{  
    int fd, num;
char rd_ch[BUFFER_LEN];
fd_set rfds, wfds; /* 读/写文件描述符集 */
 
   /* 以非阻塞、只读方式打开设备文件:/dev/globalfifo */
fd = open(DEV_NAME, O_RDONLY | O_NONBLOCK);
if (fd != -1) {
/* FIFO清0 */
if (ioctl(fd, FIFO_CLEAR, 0) < 0)
            printf("ioctl command failed\n");

while (1) {
            FD_ZERO(&rfds);//清除一个读文件描述符集合
            FD_ZERO(&wfds);
            FD_SET(fd, &rfds);//将一个文件描述符fd加入读文件描述符集合中
            FD_SET(fd, &wfds);

            select(fd + 1, &rfds, &wfds, NULL, NULL);// 多路复用IO select(应用程序)->poll(驱动程序)
            /* 数据可获得 */
            if (FD_ISSET(fd, &rfds))// 判断文件描述符是否被置位:可读
                printf("Poll monitor:can be read\n");
            /* 数据可写入 */
            if (FD_ISSET(fd, &wfds))// 判断文件描述符是否被置位:可写
                printf("Poll monitor:can be written\n");
       }
} else {
printf("Device open failure\n");
}
    
close(fd);

}      

6、验证结果:

1)sudo insmod vi_plat_dev .ko  

2)sudo insmod  vi_plat_dri.ko

3)修改设备文件权限 sudo chmod 0777 /dev/global_fifo

4)./test

结论:当没有任何输入,即FIFO为空时,程序不断地输出Poll monitor:can be written,通过echo向/dev/global_fifo写入一些数据后,将输出Poll monitor:can be read和Poll monitor:can be written,如果不断地通过echo向/dev/global_fifo写入数据直至写满FIFO,则发现pollmonitor程序将只输出Poll monitor:can be read。对于globalfifo而言,不会出现既不能读,又不能写的情况。


猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80482580
今日推荐