#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#define DEVICE_NAME "chardev"
#define DALIGN (sizeof(unsigned long) - 1)
static ssize_t chardev_read(struct file *filp, char *buf, size_t count, loff_t *offp);
static ssize_t chardev_write(struct file *filp, const char *buf, size_t count, loff_t *offp);
static struct file_operations chardev_fops = {
.read = chardev_read,
.write = chardev_write,
};
static struct cdev *cdevp = NULL;
static dev_t devno;
struct buffer {
unsigned int dlen;
struct buffer *next;
char data[0];
};
struct buffer_head {
struct buffer *head;
struct buffer *tail;
};
static struct buffer_head h = {
.head = NULL,
.tail = NULL,
};
static void item_enqueue_tail(struct buffer_head *h, struct buffer *item)
{
if (h->head == NULL)
h->head = h->tail = item;
else {
h->tail->next = item;
h->tail = item;
}
}
static struct buffer * item_dequeue(struct buffer_head *h)
{
struct buffer *item = NULL;
if (h->head) {
item = h->head;
h->head = h->head->next;
if (h->head == NULL)
h->tail = NULL;
}
return item;
}
static void destroy_queue(void)
{
struct buffer *item;
while ((item = item_dequeue(&h)) != NULL)
kfree(item);
}
static struct buffer * alloc_mem(size_t dlen)
{
struct buffer *item;
size_t len;
len = (sizeof(struct buffer) + dlen + DALIGN) & ~DALIGN;
item = kmalloc(len, GFP_KERNEL);
if (item) {
memset(item, '\0', len);
item->next = NULL;
item->dlen = dlen;
}
return item;
}
static ssize_t chardev_read(struct file *filp, char *buf, size_t count, loff_t *offp)
{
struct buffer *item = NULL;
unsigned long n;
item = item_dequeue(&h);
if (item) {
n = copy_to_user(buf, item->data, item->dlen);
kfree(item);
if (n)
return (item->dlen - n);
else
return item->dlen;
}
return 0;
}
static ssize_t chardev_write(struct file *filp, const char *buf, size_t count, loff_t *offp)
{
struct buffer *item = NULL;
unsigned long n;
item = alloc_mem(count);
if (!item)
goto alloc_err;
n = copy_from_user(item->data, buf, count);
if (n)
goto copy_err;
item_enqueue_tail(&h, item);
return count;
copy_err:
kfree(item);
alloc_err:
return -1;
}
static int __init chardev_init(void)
{
int ret;
ret = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
if (ret < 0)
goto err;
cdevp = cdev_alloc();
if (!cdevp)
goto alloc_err;
cdev_init(cdevp, &chardev_fops);
ret = cdev_add(cdevp, devno, 1);
if (ret < 0)
goto add_err;
return 0;
add_err:
cdev_del(cdevp);
alloc_err:
unregister_chrdev_region(devno, 1);
err:
return ret;
}
static void __exit chardev_exit(void)
{
destroy_queue();
cdev_del(cdevp);
unregister_chrdev_region(devno, 1);
}
MODULE_LICENSE("GPL");
module_init(chardev_init);
module_exit(chardev_exit);
|