/* * Ring buffer character device - example for Operating System course * * Copyright (C) 2016, 2017, 2018 Krzysztof Mazur * * Add your copyright here and change MODULE_AUTHOR. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #define pr_fmt(fmt) "ringdev: " fmt #include #include #include #include #include #include #include #include /* * mutex used for access synchronization to buffer (ringdev_buf and ringdev_len) */ static struct mutex ringdev_lock; struct buf { struct list_head list; size_t len; unsigned long count; char data[0]; }; static LIST_HEAD(list); static DECLARE_WAIT_QUEUE_HEAD(wq); struct reader { struct buf *buf; unsigned int read; }; static int ringdev_open(struct inode *inode, struct file *filp) { struct reader *r; r = kmalloc(sizeof(*r), GFP_KERNEL); if (!r) return -ENOMEM; mutex_lock(&ringdev_lock); r->buf = list_entry(list.prev, struct buf, list); r->buf->count++; r->read = r->buf->count; mutex_unlock(&ringdev_lock); filp->private_data = r; return 0; } static ssize_t ringdev_read(struct file *filp, char __user *buf, size_t count, loff_t *off) { struct reader *r = filp->private_data; ssize_t ret = 0; mutex_lock(&ringdev_lock); while (r->read >= r->buf->len) { struct buf *old; while (r->buf->list.next == &list) { mutex_unlock(&ringdev_lock); if (filp->f_flags & O_NONBLOCK) return -EAGAIN; ret = wait_event_interruptible(wq, r->buf->list.next != &list); if (ret < 0) return ret; mutex_lock(&ringdev_lock); } old = r->buf; r->buf = list_entry(old->list.next, struct buf, list); r->buf->count++; old->count--; if (!old->count) { list_del(&old->list); kfree(old); } r->read = 0; } if (count > r->buf->len - r->read) count = r->buf->len - r->read; ret = -EFAULT; if (copy_to_user(buf, r->buf->data + r->read, count)) goto out_unlock; r->read += count; ret = count; out_unlock: mutex_unlock(&ringdev_lock); return ret; } static ssize_t ringdev_write(struct file *filp, const char __user *buf, size_t count, loff_t *off) { struct buf *b; if (count > KMALLOC_MAX_SIZE - sizeof(*b)) count = KMALLOC_MAX_SIZE - sizeof(*b); b = kmalloc(sizeof(*b) + count, GFP_KERNEL); if (!b) return -ENOMEM; if (copy_from_user(b->data, buf, count)) return -EFAULT; b->count = 0; b->len = count; mutex_lock(&ringdev_lock); list_add_tail(&b->list, &list); mutex_unlock(&ringdev_lock); wake_up_interruptible(&wq); return count; } static int ringdev_release(struct inode *inode, struct file *filp) { kfree(filp->private_data); return 0; } static const struct file_operations ringdev_fops = { .owner = THIS_MODULE, .open = ringdev_open, .read = ringdev_read, .write = ringdev_write, .release = ringdev_release, }; static struct miscdevice ringdev_miscdevice = { .minor = MISC_DYNAMIC_MINOR, .name = "ringdev", .fops = &ringdev_fops }; static int __init ringdev_init(void) { int ret; mutex_init(&ringdev_lock); ret = ringdev_write(NULL, NULL, 0, NULL); if (ret < 0) return ret; ret = misc_register(&ringdev_miscdevice); if (ret < 0) { pr_err("can't register miscdevice.\n"); return ret; } pr_info("minor %d\n", ringdev_miscdevice.minor); return 0; } static void __exit ringdev_exit(void) { misc_deregister(&ringdev_miscdevice); mutex_destroy(&ringdev_lock); } module_init(ringdev_init); module_exit(ringdev_exit); MODULE_DESCRIPTION("Ring buffer device"); MODULE_AUTHOR("Real Name"); MODULE_LICENSE("GPL");