#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vser.h" #define VSER_MAJOR 256 #define VSER_MINOR 0 #define VSER_DEV_CNT 1 #define VSER_DEV_NAME "vser" #define VSER_FIFO_SIZE 32 struct vser_dev { wait_queue_head_t rwqh; struct fasync_struct *fapp; atomic_t available; unsigned int baud; struct option opt; struct cdev cdev; }; DEFINE_KFIFO(vsfifo, char, VSER_FIFO_SIZE); static struct vser_dev vsdev; static void vser_work(struct work_struct *work); DECLARE_WORK(vswork, vser_work); static int vser_fasync(int fd, struct file *filp, int on); static int vser_open(struct inode *inode, struct file *filp) { if (atomic_dec_and_test(&vsdev.available)) return 0; else { atomic_inc(&vsdev.available); return -EBUSY; } } static int vser_release(struct inode *inode, struct file *filp) { vser_fasync(-1, filp, 0); atomic_inc(&vsdev.available); return 0; } static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { int ret; int len; char tbuf[VSER_FIFO_SIZE]; len = count > sizeof(tbuf) ? sizeof(tbuf) : count; spin_lock(&vsdev.rwqh.lock); if (kfifo_is_empty(&vsfifo)) { if (filp->f_flags & O_NONBLOCK) { spin_unlock(&vsdev.rwqh.lock); return -EAGAIN; } if (wait_event_interruptible_locked(vsdev.rwqh, !kfifo_is_empty(&vsfifo))) { spin_unlock(&vsdev.rwqh.lock); return -ERESTARTSYS; } } len = kfifo_out(&vsfifo, tbuf, len); spin_unlock(&vsdev.rwqh.lock); ret = copy_to_user(buf, tbuf, len); return len - ret; } static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos) { int ret; int len; char *tbuf[VSER_FIFO_SIZE]; len = count > sizeof(tbuf) ? sizeof(tbuf) : count; ret = copy_from_user(tbuf, buf, len); return len - ret; } static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { if (_IOC_TYPE(cmd) != VS_MAGIC) return -ENOTTY; switch (cmd) { case VS_SET_BAUD: vsdev.baud = arg; break; case VS_GET_BAUD: arg = vsdev.baud; break; case VS_SET_FFMT: if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option))) return -EFAULT; break; case VS_GET_FFMT: if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option))) return -EFAULT; break; default: return -ENOTTY; } return 0; } static unsigned int vser_poll(struct file *filp, struct poll_table_struct *p) { int mask = POLLOUT | POLLWRNORM; poll_wait(filp, &vsdev.rwqh, p); spin_lock(&vsdev.rwqh.lock); if (!kfifo_is_empty(&vsfifo)) mask |= POLLIN | POLLRDNORM; spin_unlock(&vsdev.rwqh.lock); return mask; } static ssize_t vser_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos) { size_t read = 0; unsigned long i; ssize_t ret; for (i = 0; i < nr_segs; i++) { ret = vser_read(iocb->ki_filp, iov[i].iov_base, iov[i].iov_len, &pos); if (ret < 0) break; read += ret; } return read ? read : -EFAULT; } static ssize_t vser_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos) { size_t written = 0; unsigned long i; ssize_t ret; for (i = 0; i < nr_segs; i++) { ret = vser_write(iocb->ki_filp, iov[i].iov_base, iov[i].iov_len, &pos); if (ret < 0) break; written += ret; } return written ? written : -EFAULT; } static int vser_fasync(int fd, struct file *filp, int on) { return fasync_helper(fd, filp, on, &vsdev.fapp); } static irqreturn_t vser_handler(int irq, void *dev_id) { schedule_work(&vswork); return IRQ_HANDLED; } static void vser_work(struct work_struct *work) { char data; get_random_bytes(&data, sizeof(data)); data %= 26; data += 'A'; spin_lock(&vsdev.rwqh.lock); if (!kfifo_is_full(&vsfifo)) if(!kfifo_in(&vsfifo, &data, sizeof(data))) printk(KERN_ERR "vser: kfifo_in failure\n"); if (!kfifo_is_empty(&vsfifo)) { spin_unlock(&vsdev.rwqh.lock); wake_up_interruptible(&vsdev.rwqh); kill_fasync(&vsdev.fapp, SIGIO, POLL_IN); } else spin_unlock(&vsdev.rwqh.lock); } static struct file_operations vser_ops = { .owner = THIS_MODULE, .open = vser_open, .release = vser_release, .read = vser_read, .write = vser_write, .unlocked_ioctl = vser_ioctl, .poll = vser_poll, .aio_read = vser_aio_read, .aio_write = vser_aio_write, .fasync = vser_fasync, }; static int __init vser_init(void) { int ret; dev_t dev; dev = MKDEV(VSER_MAJOR, VSER_MINOR); ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME); if (ret) goto reg_err; cdev_init(&vsdev.cdev, &vser_ops); vsdev.cdev.owner = THIS_MODULE; vsdev.baud = 115200; vsdev.opt.datab = 8; vsdev.opt.parity = 0; vsdev.opt.stopb = 1; ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT); if (ret) goto add_err; init_waitqueue_head(&vsdev.rwqh); ret = request_irq(167, vser_handler, IRQF_TRIGGER_HIGH | IRQF_SHARED, "vser", &vsdev); if (ret) goto irq_err; atomic_set(&vsdev.available, 1); return 0; irq_err: cdev_del(&vsdev.cdev); add_err: unregister_chrdev_region(dev, VSER_DEV_CNT); reg_err: return ret; } static void __exit vser_exit(void) { dev_t dev; dev = MKDEV(VSER_MAJOR, VSER_MINOR); free_irq(167, &vsdev); cdev_del(&vsdev.cdev); unregister_chrdev_region(dev, VSER_DEV_CNT); } module_init(vser_init); module_exit(vser_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Kevin Jiang "); MODULE_DESCRIPTION("A simple character device driver"); MODULE_ALIAS("virtual-serial");