ee0686930da97283b2bc5a057db0661ac24242d9
[archipelago] / xseg / sys / kernel / segdev.c
1 /*
2  *
3  */
4
5 #include <linux/kernel.h>
6 #include <linux/module.h>
7 #include <linux/moduleparam.h>
8 #include <linux/init.h>
9 #include <linux/mm.h>
10 #include <linux/fs.h>
11 #include <linux/init.h>
12 #include <linux/list.h>
13 #include <linux/cdev.h>
14 #include <linux/poll.h>
15 #include <linux/slab.h>
16 #include <linux/sched.h>
17 #include <linux/ioctl.h>
18 #include <linux/types.h>
19 #include <linux/module.h>
20 #include <linux/mmzone.h>
21 #include <linux/vmalloc.h>
22 #include <linux/spinlock.h>
23 #include <linux/wait.h>
24
25 #include "segdev.h"
26
27 static struct segdev segdev;
28
29 int segdev_create_segment(struct segdev *dev, u64 segsize, char reserved)
30 {
31         void *segment;
32         int ret = mutex_lock_interruptible(&dev->mutex);
33         if (ret)
34                 goto out;
35
36         ret = -EBUSY;
37         if (dev->segment)
38                 goto out_unlock;
39
40         /* vmalloc can handle large sizes */
41         ret = -ENOMEM;
42         segment = vmalloc(segsize);
43         if (!segment)
44                 goto out_unlock;
45
46         dev->segsize = segsize;
47         dev->segment = segment;
48         memset(dev->segment, 0, segsize);
49         set_bit(SEGDEV_READY, &dev->flags);
50         if (reserved)
51                 set_bit(SEGDEV_RESERVED, &dev->flags);
52         ret = 0;
53
54 out_unlock:
55         mutex_unlock(&dev->mutex);
56 out:
57         return ret;
58 }
59
60 EXPORT_SYMBOL(segdev_create_segment);
61
62 int segdev_destroy_segment(struct segdev *dev)
63 {
64         int ret = mutex_lock_interruptible(&dev->mutex);
65         if (ret)
66                 goto out;
67
68         /* VERIFY:
69          * The segment trully dies when everyone in userspace has unmapped it.
70          * However, the kernel mapping is immediately destroyed.
71          * Kernel users are notified to abort via switching of SEGDEV_READY.
72          * The mapping deallocation is performed when all kernel users
73          * have stopped using the segment as reported by usercount.
74         */
75
76         ret = -EINVAL;
77         if (!dev->segment)
78                 goto out_unlock;
79
80         ret = -EBUSY;
81         if (test_bit(SEGDEV_RESERVED, &dev->flags))
82                 goto out_unlock;
83
84         clear_bit(SEGDEV_READY, &dev->flags);
85         ret = wait_event_interruptible(dev->wq, atomic_read(&dev->usercount) <= 1);
86         if (ret)
87                 goto out_unlock;
88
89         vfree(dev->segment);
90         dev->segment = NULL;
91         dev->segsize = 0;
92         ret = 0;
93
94 out_unlock:
95         mutex_unlock(&dev->mutex);
96         set_bit(SEGDEV_READY, &dev->flags);
97 out:
98         return ret;
99 }
100
101 EXPORT_SYMBOL(segdev_destroy_segment);
102
103 struct segdev *segdev_get(int minor)
104 {
105         struct segdev *dev = ERR_PTR(-ENODEV);
106         if (minor)
107                 goto out;
108
109         dev = &segdev;
110         atomic_inc(&dev->usercount);
111         if (!test_bit(SEGDEV_READY, &dev->flags))
112                 goto fail_busy;
113 out:
114         return dev;
115
116 fail_busy:
117         atomic_dec(&dev->usercount);
118         dev = ERR_PTR(-EBUSY);
119         goto out;
120 }
121
122 EXPORT_SYMBOL(segdev_get);
123
124 void segdev_put(struct segdev *dev)
125 {
126         atomic_dec(&dev->usercount);
127         wake_up(&dev->wq);
128         /* ain't all this too heavy ? */
129 }
130
131 EXPORT_SYMBOL(segdev_put);
132
133 /* ********************* */
134 /* ** File Operations ** */
135 /* ********************* */
136
137 struct segdev_file {
138         int minor;
139 };
140
141 static int segdev_open(struct inode *inode, struct file *file)
142 {
143         struct segdev_file *vf = kmalloc(sizeof(struct segdev_file), GFP_KERNEL);
144         if (!vf)
145                 return -ENOMEM;
146         vf->minor = 0;
147         file->private_data = vf;
148         return 0;
149 }
150
151 static int segdev_release(struct inode *inode, struct file *file)
152 {
153         struct segdev_file *vf = file->private_data;
154         kfree(vf);
155         return 0;
156 }
157
158 static long segdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
159 {
160         struct segdev *dev;
161         char *seg;
162         long size;
163         int ret = -EINVAL;
164
165         switch (cmd) {
166
167         case SEGDEV_IOC_CREATESEG:
168                 dev = segdev_get(0);
169                 ret = IS_ERR(dev) ? PTR_ERR(dev) : 0;
170                 if (ret)
171                         goto out;
172
173                 ret = segdev_create_segment(dev, (u64)arg, 0);
174                 segdev_put(dev);
175                 goto out;
176
177         case SEGDEV_IOC_DESTROYSEG:
178                 dev = segdev_get(0);
179                 ret = segdev_destroy_segment(&segdev);
180                 segdev_put(dev);
181                 goto out;
182
183         case SEGDEV_IOC_SEGSIZE:
184                 dev = segdev_get(0);
185
186                 ret = IS_ERR(dev) ? PTR_ERR(dev) : 0;
187                 if (ret)
188                         goto out;
189
190                 size = dev->segsize;
191                 seg = dev->segment;
192                 segdev_put(dev);
193
194                 ret = -ENODEV;
195                 if (!seg)
196                         goto out;
197
198                 return size;
199         }
200
201 out:
202         return ret;
203 }
204
205 static ssize_t segdev_read(struct file *file, char __user *buf,
206                            size_t count, loff_t *f_pos)
207 {
208         return 0;
209 }
210
211 static ssize_t segdev_write(struct file *file, const char __user *buf,
212                             size_t count, loff_t *f_pos)
213 {
214         struct segdev_file *vf = file->private_data;
215         struct segdev *dev = segdev_get(vf->minor);
216         int ret = -ENODEV;
217         if (!dev)
218                 goto out;
219
220         if (count > SEGDEV_BUFSIZE)
221                 count = SEGDEV_BUFSIZE;
222
223         ret = copy_from_user(dev->buffer, buf, count);
224         if (ret < 0)
225                 goto out;
226
227         dev->buffer_index = count - ret;
228
229         ret = 0;
230         if (dev->callback)
231                 dev->callback(dev);
232         else
233                 ret = -ENOSYS;
234
235         dev->buffer_index = 0;
236         segdev_put(dev);
237 out:
238         return ret;
239 }
240
241 static int segdev_mmap(struct file *file, struct vm_area_struct *vma)
242 {
243         struct segdev_file *vf = file->private_data;
244         struct segdev *dev;
245         size_t size = vma->vm_end - vma->vm_start;
246         unsigned long start = vma->vm_start, end = start + size;
247         char *ptr;
248         int ret = -ENODEV;
249
250         dev = segdev_get(vf->minor);
251         if (IS_ERR(dev))
252                 goto out;
253
254         ptr = dev->segment;
255         if (!ptr)
256                 goto out_put;
257
258         ret = -EINVAL;
259
260         /* do not allow offset mappings, for now */
261         if (vma->vm_pgoff || size > dev->segsize)
262                 goto out_put;
263
264         /* allow only shared, read-write mappings */
265         if (!(vma->vm_flags & VM_SHARED))
266                 goto out_put;
267
268         /* the segment is vmalloc() so we have to iterate through
269          * all pages and laboriously map them one by one. */
270         for (; start < end; start += PAGE_SIZE, ptr += PAGE_SIZE) {
271                 ret = remap_pfn_range(vma, start, vmalloc_to_pfn(ptr),
272                                       PAGE_SIZE, vma->vm_page_prot);
273                 if (ret)
274                         goto out_put; /* mmap syscall should clean up, right? */
275         }
276
277         ret = 0;
278
279 out_put:
280         segdev_put(dev);
281 out:
282         return ret;
283 }
284
285 static struct file_operations segdev_ops = 
286 {
287         .owner          = THIS_MODULE,
288         .open           = segdev_open,
289         .release        = segdev_release,
290         .read           = segdev_read,
291         .write          = segdev_write,
292         .mmap           = segdev_mmap,
293         .unlocked_ioctl = segdev_ioctl,
294 };
295
296
297 /* *************************** */
298 /* ** Module Initialization ** */
299 /* *************************** */
300
301 static void segdev_init(struct segdev *dev, int minor)
302 {
303         dev->minor = 0;
304         dev->segment = NULL;
305         dev->segsize = 0;
306         dev->flags = 0;
307         atomic_set(&dev->usercount, 0);
308         init_waitqueue_head(&dev->wq);
309         cdev_init(&dev->cdev, &segdev_ops);
310         mutex_init(&dev->mutex);
311         spin_lock_init(&dev->lock);
312         dev->cdev.owner = THIS_MODULE;
313         set_bit(SEGDEV_READY, &dev->flags);
314 }
315
316 int __init segdev_mod_init(void)
317 {
318         int ret;
319         dev_t dev_no = MKDEV(SEGDEV_MAJOR, 0);
320         ret = register_chrdev_region(dev_no, 1, "segdev");
321         if (ret < 0)
322                 goto out;
323
324         segdev_init(&segdev, 0);
325         ret = cdev_add(&segdev.cdev, dev_no, 1);
326         if (ret < 0)
327                 goto out_unregister;
328
329         return ret;
330
331 out_unregister:
332         unregister_chrdev_region(dev_no, 1);
333 out:
334         return ret;
335 }
336
337 void __exit segdev_mod_exit(void)
338 {
339         dev_t dev_no = MKDEV(SEGDEV_MAJOR, 0);
340         segdev_destroy_segment(&segdev);
341         cdev_del(&segdev.cdev);
342         unregister_chrdev_region(dev_no, 1);
343 }
344
345 module_init(segdev_mod_init);
346 module_exit(segdev_mod_exit);
347
348 MODULE_DESCRIPTION("segdev");
349 MODULE_AUTHOR("XSEG");
350 MODULE_LICENSE("GPL");
351