summaryrefslogtreecommitdiffstats
path: root/mod_add.c
blob: 096baafcac264d512457cb2b6094c241bdedbe28 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/semaphore.h>

MODULE_LICENSE("GPL");

#define DEV_NAME "sad_dev"

static int sad_open(struct inode *, struct file *);
static int sad_release(struct inode *, struct file *);
static ssize_t sad_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t sad_write(struct file *, const char __user *, size_t, loff_t *);

static dev_t dev;
static struct cdev *cdev;
static int device_open_count = 0;

struct sad_buf {
    char *buf;
    size_t size;
    struct semaphore lock;
} *buf;

static struct file_operations sad_fops = {
    .owner = THIS_MODULE,
    .read = sad_read,
    .write = sad_write,
    .open = sad_open,
    .release = sad_release
};


static ssize_t sad_read(struct file *flip, char __user *user_buf, size_t len, loff_t *offset) {
    size_t bytes_left, read_size, uncopied;

    if (down_interruptible(&buf->lock))
        return -ERESTARTSYS;

    if (*offset >= buf->size) {
        up(&buf->lock);
        return 0;
    }

    bytes_left = buf->size - *offset;
    read_size = min(bytes_left, len);
    uncopied = copy_to_user(user_buf, buf->buf + *offset, read_size);

    *offset += read_size - uncopied;

    up(&buf->lock);

    return read_size - uncopied;
}

static ssize_t sad_write(struct file *flip, const char __user *user_buf, size_t len, loff_t *offset) {
    size_t uncopied;

    if (down_interruptible(&buf->lock))
        return -ERESTARTSYS;

    if (buf->buf)
        kfree(buf->buf);

    buf->buf = kmalloc(len, GFP_KERNEL);
    if (!buf->buf) {
        up(&buf->lock);
        return -ENOMEM;
    }

    buf->size = len;

    *offset = 0;

    uncopied = copy_from_user(buf->buf, user_buf, len);

    up(&buf->lock);

    return len - uncopied;
}

static int sad_open(struct inode *inode, struct file *file) {
    device_open_count++;
    try_module_get(THIS_MODULE);
    return 0;
}

static int sad_release(struct inode *inode, struct file *file) {
    device_open_count--;
    if (device_open_count == 0)
        module_put(THIS_MODULE);
    return 0;
}

static int __init sad_init(void) {
    int rc;

    const unsigned int minor = 0;
    const unsigned int count = 1;

    buf = kzalloc(sizeof(*buf), GFP_KERNEL);
    if (!buf)
        return -ENOMEM;

    sema_init(&buf->lock, 1);

    rc = alloc_chrdev_region(&dev, minor, count, DEV_NAME);
    if (rc < 0) {
        printk(KERN_ALERT "Could not register device: %d\n", rc);
        return rc;
    } else {
        printk(KERN_INFO "Allocated maj %d\n", MAJOR(dev));
    }

    cdev = cdev_alloc();
    cdev->owner = THIS_MODULE;
    cdev->ops = &sad_fops;
    rc = cdev_add(cdev, dev, 1);

    if (rc) {
        printk(KERN_ALERT "Could not cdev_add: %d\n", rc);
        return rc;
    }

    return 0;
}
static void __exit sad_exit(void) {
    cdev_del(cdev);
    if (buf->buf)
        kfree(buf->buf);
    kfree(buf);
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "okay, bye\n");
}

module_init(sad_init);
module_exit(sad_exit);