Merge branch 'wip-xinfo-fixed' into xseg
[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         int ret = -ENODEV;
217         if (!dev)
218                 goto out;
219
220         ret = -ENOSYS;
221         if (dev->callback)
222                 ret = dev->callback(dev->callarg);
223
224         xsegdev_put(dev);
225 out:
226         return ret;
227 }
228
229 static int xsegdev_mmap(struct file *file, struct vm_area_struct *vma)
230 {
231         struct xsegdev_file *vf = file->private_data;
232         struct xsegdev *dev;
233         size_t size = vma->vm_end - vma->vm_start;
234         unsigned long start = vma->vm_start, end = start + size;
235         char *ptr;
236         int ret = -ENODEV;
237
238         dev = xsegdev_get(vf->minor);
239         if (IS_ERR(dev))
240                 goto out;
241
242         ptr = dev->segment;
243         if (!ptr)
244                 goto out_put;
245
246         ret = -EINVAL;
247
248         /* do not allow offset mappings, for now */
249         if (vma->vm_pgoff || size > dev->segsize)
250                 goto out_put;
251
252         /* allow only shared, read-write mappings */
253         if (!(vma->vm_flags & VM_SHARED))
254                 goto out_put;
255
256         /* the segment is vmalloc() so we have to iterate through
257          * all pages and laboriously map them one by one. */
258         for (; start < end; start += PAGE_SIZE, ptr += PAGE_SIZE) {
259                 ret = remap_pfn_range(vma, start, vmalloc_to_pfn(ptr),
260                                       PAGE_SIZE, vma->vm_page_prot);
261                 if (ret)
262                         goto out_put; /* mmap syscall should clean up, right? */
263         }
264
265         ret = 0;
266
267 out_put:
268         xsegdev_put(dev);
269 out:
270         return ret;
271 }
272
273 static struct file_operations xsegdev_ops = 
274 {
275         .owner          = THIS_MODULE,
276         .open           = xsegdev_open,
277         .release        = xsegdev_release,
278         .read           = xsegdev_read,
279         .write          = xsegdev_write,
280         .mmap           = xsegdev_mmap,
281         .unlocked_ioctl = xsegdev_ioctl,
282 };
283
284
285 /* *************************** */
286 /* ** Module Initialization ** */
287 /* *************************** */
288
289 static void xsegdev_init(struct xsegdev *dev, int minor)
290 {
291         dev->minor = 0;
292         dev->segment = NULL;
293         dev->segsize = 0;
294         dev->flags = 0;
295         atomic_set(&dev->usercount, 0);
296         init_waitqueue_head(&dev->wq);
297         cdev_init(&dev->cdev, &xsegdev_ops);
298         mutex_init(&dev->mutex);
299         spin_lock_init(&dev->lock);
300         dev->cdev.owner = THIS_MODULE;
301         set_bit(XSEGDEV_READY, &dev->flags);
302 }
303
304 int __init xsegdev_mod_init(void)
305 {
306         int ret;
307         dev_t dev_no = MKDEV(XSEGDEV_MAJOR, 0);
308         ret = register_chrdev_region(dev_no, 1, "xsegdev");
309         if (ret < 0)
310                 goto out;
311
312         xsegdev_init(&xsegdev, 0);
313         ret = cdev_add(&xsegdev.cdev, dev_no, 1);
314         if (ret < 0)
315                 goto out_unregister;
316
317         return ret;
318
319 out_unregister:
320         unregister_chrdev_region(dev_no, 1);
321 out:
322         return ret;
323 }
324
325 void __exit xsegdev_mod_exit(void)
326 {
327         dev_t dev_no = MKDEV(XSEGDEV_MAJOR, 0);
328         xsegdev_destroy_segment(&xsegdev);
329         cdev_del(&xsegdev.cdev);
330         unregister_chrdev_region(dev_no, 1);
331 }
332
333 module_init(xsegdev_mod_init);
334 module_exit(xsegdev_mod_exit);
335
336 MODULE_DESCRIPTION("xsegdev");
337 MODULE_AUTHOR("XSEG");
338 MODULE_LICENSE("GPL");
339