/*
* A mymem driver as an example of char device drivers
* This example is to introduce poll,blocking and non-blocking access
*
* The initial developer of the original code is Baohua Song
* <
[email protected]>. All Rights Reserved.
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#include <linux/device.h>
#define MYMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
#define MYMEM_MAJOR 0
static int mymem_major = MYMEM_MAJOR;
static struct class *mymem_class = NULL;
#define DEVICE_NAME "mymem"
struct mymem_dev {
struct cdev cdev;
unsigned int current_len;
unsigned char mem[MYMEM_SIZE];
struct semaphore sem;
wait_queue_head_t r_wait;
wait_queue_head_t w_wait;
};
struct mymem_dev *mymem_devp;
int mymem_open(struct inode *inode, struct file *filp)
{
filp->private_data = mymem_devp;
return 0;
}
int mymem_release(struct inode *inode, struct file *filp)
{
return 0;
}
static int mymem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct mymem_dev *dev = filp->private_data;
switch (cmd) {
case MEM_CLEAR:
down(&dev->sem);
dev->current_len = 0;
memset(dev->mem,0,MYMEM_SIZE);
up(&dev->sem);
printk(KERN_INFO "mymem is set to zero\n");
break;
default:
return - EINVAL;
}
return 0;
}
static unsigned int mymem_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct mymem_dev *dev = filp->private_data;
down(&dev->sem);
poll_wait(filp, &dev->r_wait, wait);
poll_wait(filp, &dev->w_wait, wait);
if (dev->current_len != 0) {
mask |= POLLIN | POLLRDNORM;
}
if (dev->current_len != MYMEM_SIZE) {
mask |= POLLOUT | POLLWRNORM;
}
up(&dev->sem);
return mask;
}
static ssize_t mymem_read(struct file *filp, char __user *buf, size_t count,
loff_t *ppos)
{
int ret;
struct mymem_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
down(&dev->sem);
add_wait_queue(&dev->r_wait, &wait);
if (dev->current_len == 0) {
if (filp->f_flags &O_NONBLOCK) {
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
up(&dev->sem);
schedule();
if (signal_pending(current)) {
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
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:
up(&dev->sem);
out2:
remove_wait_queue(&dev->w_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
static ssize_t mymem_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
struct mymem_dev *dev = filp->private_data;
int ret;
DECLARE_WAITQUEUE(wait, current);
down(&dev->sem);
add_wait_queue(&dev->w_wait, &wait);
if (dev->current_len == MYMEM_SIZE) {
if (filp->f_flags &O_NONBLOCK) {
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
up(&dev->sem);
schedule();
if (signal_pending(current)) {
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
if (count > MYMEM_SIZE - dev->current_len)
count = MYMEM_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:
up(&dev->sem);
out2:
remove_wait_queue(&dev->w_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
static const struct file_operations mymem_fops = {
.owner = THIS_MODULE,
.read = mymem_read,
.write = mymem_write,
.ioctl = mymem_ioctl,
.poll = mymem_poll,
.open = mymem_open,
.release = mymem_release,
};
static void mymem_setup_cdev(struct mymem_dev *dev, int index)
{
int err, devno = MKDEV(mymem_major, index);
cdev_init(&dev->cdev, &mymem_fops);//初始化字符设备
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);//将字符设备注册到系统
if (err)
printk(KERN_NOTICE "Error %d adding mem%d", err, index);
}
int mymem_init(void)
{
int ret,err;
struct device *temp = NULL;
dev_t devno = MKDEV(mymem_major, 0);
/*
静态分配设备号
*/
if (mymem_major)
ret = register_chrdev_region(devno, 1, "mymem");
else {
/*
动态分配设备号。设备号由系统指定
*/
ret = alloc_chrdev_region(&devno, 0, 1, "mymem");
mymem_major = MAJOR(devno);
}
if (ret < 0)
return ret;
mymem_devp = kmalloc(sizeof(struct mymem_dev), GFP_KERNEL);
if (!mymem_devp) {
ret = - ENOMEM;
goto fail_malloc;
}
memset(mymem_devp, 0, sizeof(struct mymem_dev));
mymem_setup_cdev(mymem_devp, 0);//初始化并注册字符设备
/*
在/sys/class目录下创建device_name设备的类目
*/
mymem_class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(mymem_class)){
err = PTR_ERR(mymem_class);
printk(KERN_ALERT"FAILED TO create mymem_class\r\n");
goto destroy_cdev;
}
/*
在/dev/目录下和/sys/class/mymem目录下分别创建设备文件
*/
temp = device_create(mymem_class,NULL,devno,"%s",DEVICE_NAME);
if(IS_ERR(temp)){
err = PTR_ERR(temp);
printk(KERN_ALERT"FAILED TO create dev_mymem\r\n");
goto destroy_class;
}
init_MUTEX(&mymem_devp->sem);
init_waitqueue_head(&mymem_devp->r_wait);
init_waitqueue_head(&mymem_devp->w_wait);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return ret;
destroy_class:
class_destroy(mymem_class);
destroy_cdev:
cdev_del(&mymem_devp->cdev);
fail:
return err;
}
void mymem_exit(void)
{
cdev_del(&mymem_devp->cdev);
kfree(mymem_devp);
unregister_chrdev_region(MKDEV(mymem_major, 0), 1);
}
MODULE_AUTHOR("
[email protected]");
MODULE_LICENSE("Dual BSD/GPL");
module_param(mymem_major, int, S_IRUGO);//传递参数
module_init(mymem_init);
module_exit(mymem_exit);