root / xseg / sys / kernel / segdev.c @ 7ce25cf6
History | View | Annotate | Download (6.7 kB)
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 |
|