xsegbd: Release and error code paths fixes
[archipelago] / xseg / sys / xsegdev.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 "xsegdev.h"
26
27 static struct xsegdev xsegdev;
28
29 int xsegdev_create_segment(struct xsegdev *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(XSEGDEV_READY, &dev->flags);
50         if (reserved)
51                 set_bit(XSEGDEV_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(xsegdev_create_segment);
61
62 int xsegdev_destroy_segment(struct xsegdev *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 XSEGDEV_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(XSEGDEV_RESERVED, &dev->flags))
82                 goto out_unlock;
83
84         clear_bit(XSEGDEV_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(XSEGDEV_READY, &dev->flags);
97 out:
98         return ret;
99 }
100
101 EXPORT_SYMBOL(xsegdev_destroy_segment);
102
103 struct xsegdev *xsegdev_get(int minor)
104 {
105         struct xsegdev *dev = ERR_PTR(-ENODEV);
106         if (minor)
107                 goto out;
108
109         dev = &xsegdev;
110         atomic_inc(&dev->usercount);
111         if (!test_bit(XSEGDEV_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(xsegdev_get);
123
124 void xsegdev_put(struct xsegdev *dev)
125 {
126         atomic_dec(&dev->usercount);
127         wake_up(&dev->wq);
128         /* ain't all this too heavy ? */
129 }
130
131 EXPORT_SYMBOL(xsegdev_put);
132
133 /* ********************* */
134 /* ** File Operations ** */
135 /* ********************* */
136
137 struct xsegdev_file {
138         int minor;
139 };
140
141 static int xsegdev_open(struct inode *inode, struct file *file)
142 {
143         struct xsegdev_file *vf = kmalloc(sizeof(struct xsegdev_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 xsegdev_release(struct inode *inode, struct file *file)
152 {
153         struct xsegdev_file *vf = file->private_data;
154         kfree(vf);
155         return 0;
156 }
157
158 static long xsegdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
159 {
160         struct xsegdev *dev;
161         char *seg;
162         long size;
163         int ret = -EINVAL;
164
165         switch (cmd) {
166
167         case XSEGDEV_IOC_CREATESEG:
168                 dev = xsegdev_get(0);
169                 ret = IS_ERR(dev) ? PTR_ERR(dev) : 0;
170                 if (ret)
171                         goto out;
172
173                 ret = xsegdev_create_segment(dev, (u64)arg, 0);
174                 xsegdev_put(dev);
175                 goto out;
176
177         case XSEGDEV_IOC_DESTROYSEG:
178                 dev = xsegdev_get(0);
179                 ret = xsegdev_destroy_segment(&xsegdev);
180                 xsegdev_put(dev);
181                 goto out;
182
183         case XSEGDEV_IOC_SEGSIZE:
184                 dev = xsegdev_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                 xsegdev_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 xsegdev_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 xsegdev_write(struct file *file, const char __user *buf,
212                             size_t count, loff_t *f_pos)
213 {
214         struct xsegdev_file *vf = file->private_data;
215         struct xsegdev *dev = xsegdev_get(vf->minor);
216         struct xseg_port *port;
217         int ret = -ENODEV;
218         if (!dev)
219                 goto out;
220
221         ret = copy_from_user(&port, buf, sizeof(port));
222         if (ret < 0)
223                 goto out;
224
225         ret = -ENOSYS;
226         if (dev->callback)
227                 ret = dev->callback(port);
228
229         xsegdev_put(dev);
230 out:
231         return ret;
232 }
233
234 static int xsegdev_mmap(struct file *file, struct vm_area_struct *vma)
235 {
236         struct xsegdev_file *vf = file->private_data;
237         struct xsegdev *dev;
238         size_t size = vma->vm_end - vma->vm_start;
239         unsigned long start = vma->vm_start, end = start + size;
240         char *ptr;
241         int ret = -ENODEV;
242
243         dev = xsegdev_get(vf->minor);
244         if (IS_ERR(dev))
245                 goto out;
246
247         ptr = dev->segment;
248         if (!ptr)
249                 goto out_put;
250
251         ret = -EINVAL;
252
253         /* do not allow offset mappings, for now */
254         if (vma->vm_pgoff || size > dev->segsize)
255                 goto out_put;
256
257         /* allow only shared, read-write mappings */
258         if (!(vma->vm_flags & VM_SHARED))
259                 goto out_put;
260
261         /* the segment is vmalloc() so we have to iterate through
262          * all pages and laboriously map them one by one. */
263         for (; start < end; start += PAGE_SIZE, ptr += PAGE_SIZE) {
264                 ret = remap_pfn_range(vma, start, vmalloc_to_pfn(ptr),
265                                       PAGE_SIZE, vma->vm_page_prot);
266                 if (ret)
267                         goto out_put; /* mmap syscall should clean up, right? */
268         }
269
270         ret = 0;
271
272 out_put:
273         xsegdev_put(dev);
274 out:
275         return ret;
276 }
277
278 static struct file_operations xsegdev_ops = 
279 {
280         .owner          = THIS_MODULE,
281         .open           = xsegdev_open,
282         .release        = xsegdev_release,
283         .read           = xsegdev_read,
284         .write          = xsegdev_write,
285         .mmap           = xsegdev_mmap,
286         .unlocked_ioctl = xsegdev_ioctl,
287 };
288
289
290 /* *************************** */
291 /* ** Module Initialization ** */
292 /* *************************** */
293
294 static void xsegdev_init(struct xsegdev *dev, int minor)
295 {
296         dev->minor = 0;
297         dev->segment = NULL;
298         dev->segsize = 0;
299         dev->flags = 0;
300         atomic_set(&dev->usercount, 0);
301         init_waitqueue_head(&dev->wq);
302         cdev_init(&dev->cdev, &xsegdev_ops);
303         mutex_init(&dev->mutex);
304         spin_lock_init(&dev->lock);
305         dev->cdev.owner = THIS_MODULE;
306         set_bit(XSEGDEV_READY, &dev->flags);
307 }
308
309 int __init xsegdev_mod_init(void)
310 {
311         int ret;
312         dev_t dev_no = MKDEV(XSEGDEV_MAJOR, 0);
313         ret = register_chrdev_region(dev_no, 1, "xsegdev");
314         if (ret < 0)
315                 goto out;
316
317         xsegdev_init(&xsegdev, 0);
318         ret = cdev_add(&xsegdev.cdev, dev_no, 1);
319         if (ret < 0)
320                 goto out_unregister;
321
322         return ret;
323
324 out_unregister:
325         unregister_chrdev_region(dev_no, 1);
326 out:
327         return ret;
328 }
329
330 void __exit xsegdev_mod_exit(void)
331 {
332         dev_t dev_no = MKDEV(XSEGDEV_MAJOR, 0);
333         xsegdev_destroy_segment(&xsegdev);
334         cdev_del(&xsegdev.cdev);
335         unregister_chrdev_region(dev_no, 1);
336 }
337
338 module_init(xsegdev_mod_init);
339 module_exit(xsegdev_mod_exit);
340
341 MODULE_DESCRIPTION("xsegdev");
342 MODULE_AUTHOR("XSEG");
343 MODULE_LICENSE("GPL");
344