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