第12章 Linux设备驱动的软件架构思想之globalfifo作为platform设备

将globalfifo驱动挂接到platform总线上,要完成两个工作。

1、将globalfifo移植为platform驱动。

2、添加globalfifo这个platform设备。

    为完成将globalfifo移植到platform驱动的工作,需要在原始的globalfifo字符设备驱动中套一层

platform_driver的外壳。

注意:进行这一工作后,并没有改变globalfifo是字符设备的本质,只是将其挂接到了platform总线上。

平台驱动源文件,代码清单12.6 globalfifo添加platform_driver

代码清单12.6 为globalfifo添加platform_driver

#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"

static int globalfifo_major = GLOBALFIFO_MAJOR;
module_param(globalfifo_major, int, S_IRUGO); /* mode:S_IRUGO */

struct globalfifo_dev *globalfifo_devp = NULL;


/**
*
* 读写函数
* 让设备结构体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 = filp->private_data; // 获取私有数据指针

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)
{
struct globalfifo_dev *dev = filp->private_data;
int ret;
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 = filp->private_data;// 使用文件的私有数据访问设备结构体


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_open(struct inode *inode, struct file *filep)

filep->private_data = globalfifo_devp;// 设置私有数据,将文件的私有数据private_data指向设备结构体的实例,体现Linux的面向对象的设计思想
printk(KERN_INFO "globalfifo_open\n");
return 0; /* 成功 */
}

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

static unsigned int globalfifo_poll (struct file *filep, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filep->private_data;// 使用文件的私有数据访问设备结构体

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,
};

static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int minor)
{
int err, devno = MKDEV(globalfifo_major, minor);
cdev_init(&dev->cdev, &globalfifo_fops); // 初始化字符设备
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1); // 注册字符设备到内核
if (err)
printk(KERN_NOTICE "Error %d adding globalfifo [%d]", err, minor);
}

static int globalfifo_probe(struct platform_device *pdev)  
{  
    int ret;

    dev_t devno = MKDEV(globalfifo_major, 0);
 
    if (globalfifo_major)
        ret = register_chrdev_region(devno, 1, "globalfifo");
    else {
        ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
        globalfifo_major = MAJOR(devno);
    }

if (ret < 0)
        return ret;

    globalfifo_devp = devm_kzalloc(&pdev->dev, sizeof(*globalfifo_devp),GFP_KERNEL);
    if (!globalfifo_devp) {
        ret = -ENOMEM;
        goto fail_kernel_malloc;
    }

    globalfifo_setup_cdev(globalfifo_devp, 0);
mutex_init(&globalfifo_devp->mutex);  // 初始化互斥锁
init_waitqueue_head(&globalfifo_devp->r_wait);// 初始化读等待队列头部
init_waitqueue_head(&globalfifo_devp->w_wait);// 初始化写等待队列头部
printk(KERN_INFO "%s driver found device!!\n", __func__);   
    return 0;


fail_kernel_malloc:
    unregister_chrdev_region(devno, 1);
    return ret;

}  
  
static int globalfifo_remove(struct platform_device *pdev)  
{  
cdev_del(&globalfifo_devp->cdev);
kfree(globalfifo_devp); // 释放分配的内存空间
    unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
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");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

平台设备源文件,代码清单12.7 globalfifo添加平台设备

#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);

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

    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、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_driv.o

endif

4、驱动测试文件

#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/globalfifo"
#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);

}      

5、在用户空间中验证globalfifo设备的轮询

1)sudo insmod vi_plat_dev.ko 

2)sudo insmod  vi_plat_driv.ko

3)创建设备文件 sudo mknod -m 0666 /dev/globalfifo c 230 0

4)./test

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

6、总结:

1)module_platform_driver()宏所定义的模块加载和卸载函数仅仅通过platform_driver_register()、

platform_driver_unregister()函数进行platform_driver的注册与注销,而原先注册和注销字符设备的工作已经被移交到platform_driver的probe()和remove()成员函数中。

2)注册完globalfifo对应的platform_driver后,发现/sys/bus/platform/drivers目录下多出了一个名字叫globalfifo的子目录。

3)通过platform_add_devices()的函数把这个platform_device注册进系统。会在/sys/devices/platform目录下看到一个名字叫globalfifo的子目录,/sys/devices/platform/globalfifo中会有一个driver文件,它是指向/sys/bus/platform/drivers/globalfifo的符号链接,这证明驱动和设备匹配上了。


阅读更多
个人分类: Linux驱动开发
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭