diff options
Diffstat (limited to 'drivers/xen/gntalloc.c')
-rw-r--r-- | drivers/xen/gntalloc.c | 120 |
1 files changed, 86 insertions, 34 deletions
diff --git a/drivers/xen/gntalloc.c b/drivers/xen/gntalloc.c index e1c4c6e5b469..e2400c8963fa 100644 --- a/drivers/xen/gntalloc.c +++ b/drivers/xen/gntalloc.c | |||
@@ -74,7 +74,7 @@ MODULE_PARM_DESC(limit, "Maximum number of grants that may be allocated by " | |||
74 | "the gntalloc device"); | 74 | "the gntalloc device"); |
75 | 75 | ||
76 | static LIST_HEAD(gref_list); | 76 | static LIST_HEAD(gref_list); |
77 | static DEFINE_SPINLOCK(gref_lock); | 77 | static DEFINE_MUTEX(gref_mutex); |
78 | static int gref_size; | 78 | static int gref_size; |
79 | 79 | ||
80 | struct notify_info { | 80 | struct notify_info { |
@@ -99,6 +99,12 @@ struct gntalloc_file_private_data { | |||
99 | uint64_t index; | 99 | uint64_t index; |
100 | }; | 100 | }; |
101 | 101 | ||
102 | struct gntalloc_vma_private_data { | ||
103 | struct gntalloc_gref *gref; | ||
104 | int users; | ||
105 | int count; | ||
106 | }; | ||
107 | |||
102 | static void __del_gref(struct gntalloc_gref *gref); | 108 | static void __del_gref(struct gntalloc_gref *gref); |
103 | 109 | ||
104 | static void do_cleanup(void) | 110 | static void do_cleanup(void) |
@@ -143,15 +149,15 @@ static int add_grefs(struct ioctl_gntalloc_alloc_gref *op, | |||
143 | } | 149 | } |
144 | 150 | ||
145 | /* Add to gref lists. */ | 151 | /* Add to gref lists. */ |
146 | spin_lock(&gref_lock); | 152 | mutex_lock(&gref_mutex); |
147 | list_splice_tail(&queue_gref, &gref_list); | 153 | list_splice_tail(&queue_gref, &gref_list); |
148 | list_splice_tail(&queue_file, &priv->list); | 154 | list_splice_tail(&queue_file, &priv->list); |
149 | spin_unlock(&gref_lock); | 155 | mutex_unlock(&gref_mutex); |
150 | 156 | ||
151 | return 0; | 157 | return 0; |
152 | 158 | ||
153 | undo: | 159 | undo: |
154 | spin_lock(&gref_lock); | 160 | mutex_lock(&gref_mutex); |
155 | gref_size -= (op->count - i); | 161 | gref_size -= (op->count - i); |
156 | 162 | ||
157 | list_for_each_entry(gref, &queue_file, next_file) { | 163 | list_for_each_entry(gref, &queue_file, next_file) { |
@@ -167,7 +173,7 @@ undo: | |||
167 | */ | 173 | */ |
168 | if (unlikely(!list_empty(&queue_gref))) | 174 | if (unlikely(!list_empty(&queue_gref))) |
169 | list_splice_tail(&queue_gref, &gref_list); | 175 | list_splice_tail(&queue_gref, &gref_list); |
170 | spin_unlock(&gref_lock); | 176 | mutex_unlock(&gref_mutex); |
171 | return rc; | 177 | return rc; |
172 | } | 178 | } |
173 | 179 | ||
@@ -178,8 +184,10 @@ static void __del_gref(struct gntalloc_gref *gref) | |||
178 | tmp[gref->notify.pgoff] = 0; | 184 | tmp[gref->notify.pgoff] = 0; |
179 | kunmap(gref->page); | 185 | kunmap(gref->page); |
180 | } | 186 | } |
181 | if (gref->notify.flags & UNMAP_NOTIFY_SEND_EVENT) | 187 | if (gref->notify.flags & UNMAP_NOTIFY_SEND_EVENT) { |
182 | notify_remote_via_evtchn(gref->notify.event); | 188 | notify_remote_via_evtchn(gref->notify.event); |
189 | evtchn_put(gref->notify.event); | ||
190 | } | ||
183 | 191 | ||
184 | gref->notify.flags = 0; | 192 | gref->notify.flags = 0; |
185 | 193 | ||
@@ -189,6 +197,8 @@ static void __del_gref(struct gntalloc_gref *gref) | |||
189 | 197 | ||
190 | if (!gnttab_end_foreign_access_ref(gref->gref_id, 0)) | 198 | if (!gnttab_end_foreign_access_ref(gref->gref_id, 0)) |
191 | return; | 199 | return; |
200 | |||
201 | gnttab_free_grant_reference(gref->gref_id); | ||
192 | } | 202 | } |
193 | 203 | ||
194 | gref_size--; | 204 | gref_size--; |
@@ -251,7 +261,7 @@ static int gntalloc_release(struct inode *inode, struct file *filp) | |||
251 | 261 | ||
252 | pr_debug("%s: priv %p\n", __func__, priv); | 262 | pr_debug("%s: priv %p\n", __func__, priv); |
253 | 263 | ||
254 | spin_lock(&gref_lock); | 264 | mutex_lock(&gref_mutex); |
255 | while (!list_empty(&priv->list)) { | 265 | while (!list_empty(&priv->list)) { |
256 | gref = list_entry(priv->list.next, | 266 | gref = list_entry(priv->list.next, |
257 | struct gntalloc_gref, next_file); | 267 | struct gntalloc_gref, next_file); |
@@ -261,7 +271,7 @@ static int gntalloc_release(struct inode *inode, struct file *filp) | |||
261 | __del_gref(gref); | 271 | __del_gref(gref); |
262 | } | 272 | } |
263 | kfree(priv); | 273 | kfree(priv); |
264 | spin_unlock(&gref_lock); | 274 | mutex_unlock(&gref_mutex); |
265 | 275 | ||
266 | return 0; | 276 | return 0; |
267 | } | 277 | } |
@@ -286,21 +296,21 @@ static long gntalloc_ioctl_alloc(struct gntalloc_file_private_data *priv, | |||
286 | goto out; | 296 | goto out; |
287 | } | 297 | } |
288 | 298 | ||
289 | spin_lock(&gref_lock); | 299 | mutex_lock(&gref_mutex); |
290 | /* Clean up pages that were at zero (local) users but were still mapped | 300 | /* Clean up pages that were at zero (local) users but were still mapped |
291 | * by remote domains. Since those pages count towards the limit that we | 301 | * by remote domains. Since those pages count towards the limit that we |
292 | * are about to enforce, removing them here is a good idea. | 302 | * are about to enforce, removing them here is a good idea. |
293 | */ | 303 | */ |
294 | do_cleanup(); | 304 | do_cleanup(); |
295 | if (gref_size + op.count > limit) { | 305 | if (gref_size + op.count > limit) { |
296 | spin_unlock(&gref_lock); | 306 | mutex_unlock(&gref_mutex); |
297 | rc = -ENOSPC; | 307 | rc = -ENOSPC; |
298 | goto out_free; | 308 | goto out_free; |
299 | } | 309 | } |
300 | gref_size += op.count; | 310 | gref_size += op.count; |
301 | op.index = priv->index; | 311 | op.index = priv->index; |
302 | priv->index += op.count * PAGE_SIZE; | 312 | priv->index += op.count * PAGE_SIZE; |
303 | spin_unlock(&gref_lock); | 313 | mutex_unlock(&gref_mutex); |
304 | 314 | ||
305 | rc = add_grefs(&op, gref_ids, priv); | 315 | rc = add_grefs(&op, gref_ids, priv); |
306 | if (rc < 0) | 316 | if (rc < 0) |
@@ -343,7 +353,7 @@ static long gntalloc_ioctl_dealloc(struct gntalloc_file_private_data *priv, | |||
343 | goto dealloc_grant_out; | 353 | goto dealloc_grant_out; |
344 | } | 354 | } |
345 | 355 | ||
346 | spin_lock(&gref_lock); | 356 | mutex_lock(&gref_mutex); |
347 | gref = find_grefs(priv, op.index, op.count); | 357 | gref = find_grefs(priv, op.index, op.count); |
348 | if (gref) { | 358 | if (gref) { |
349 | /* Remove from the file list only, and decrease reference count. | 359 | /* Remove from the file list only, and decrease reference count. |
@@ -363,7 +373,7 @@ static long gntalloc_ioctl_dealloc(struct gntalloc_file_private_data *priv, | |||
363 | 373 | ||
364 | do_cleanup(); | 374 | do_cleanup(); |
365 | 375 | ||
366 | spin_unlock(&gref_lock); | 376 | mutex_unlock(&gref_mutex); |
367 | dealloc_grant_out: | 377 | dealloc_grant_out: |
368 | return rc; | 378 | return rc; |
369 | } | 379 | } |
@@ -383,7 +393,7 @@ static long gntalloc_ioctl_unmap_notify(struct gntalloc_file_private_data *priv, | |||
383 | index = op.index & ~(PAGE_SIZE - 1); | 393 | index = op.index & ~(PAGE_SIZE - 1); |
384 | pgoff = op.index & (PAGE_SIZE - 1); | 394 | pgoff = op.index & (PAGE_SIZE - 1); |
385 | 395 | ||
386 | spin_lock(&gref_lock); | 396 | mutex_lock(&gref_mutex); |
387 | 397 | ||
388 | gref = find_grefs(priv, index, 1); | 398 | gref = find_grefs(priv, index, 1); |
389 | if (!gref) { | 399 | if (!gref) { |
@@ -396,12 +406,30 @@ static long gntalloc_ioctl_unmap_notify(struct gntalloc_file_private_data *priv, | |||
396 | goto unlock_out; | 406 | goto unlock_out; |
397 | } | 407 | } |
398 | 408 | ||
409 | /* We need to grab a reference to the event channel we are going to use | ||
410 | * to send the notify before releasing the reference we may already have | ||
411 | * (if someone has called this ioctl twice). This is required so that | ||
412 | * it is possible to change the clear_byte part of the notification | ||
413 | * without disturbing the event channel part, which may now be the last | ||
414 | * reference to that event channel. | ||
415 | */ | ||
416 | if (op.action & UNMAP_NOTIFY_SEND_EVENT) { | ||
417 | if (evtchn_get(op.event_channel_port)) { | ||
418 | rc = -EINVAL; | ||
419 | goto unlock_out; | ||
420 | } | ||
421 | } | ||
422 | |||
423 | if (gref->notify.flags & UNMAP_NOTIFY_SEND_EVENT) | ||
424 | evtchn_put(gref->notify.event); | ||
425 | |||
399 | gref->notify.flags = op.action; | 426 | gref->notify.flags = op.action; |
400 | gref->notify.pgoff = pgoff; | 427 | gref->notify.pgoff = pgoff; |
401 | gref->notify.event = op.event_channel_port; | 428 | gref->notify.event = op.event_channel_port; |
402 | rc = 0; | 429 | rc = 0; |
430 | |||
403 | unlock_out: | 431 | unlock_out: |
404 | spin_unlock(&gref_lock); | 432 | mutex_unlock(&gref_mutex); |
405 | return rc; | 433 | return rc; |
406 | } | 434 | } |
407 | 435 | ||
@@ -429,26 +457,40 @@ static long gntalloc_ioctl(struct file *filp, unsigned int cmd, | |||
429 | 457 | ||
430 | static void gntalloc_vma_open(struct vm_area_struct *vma) | 458 | static void gntalloc_vma_open(struct vm_area_struct *vma) |
431 | { | 459 | { |
432 | struct gntalloc_gref *gref = vma->vm_private_data; | 460 | struct gntalloc_vma_private_data *priv = vma->vm_private_data; |
433 | if (!gref) | 461 | |
462 | if (!priv) | ||
434 | return; | 463 | return; |
435 | 464 | ||
436 | spin_lock(&gref_lock); | 465 | mutex_lock(&gref_mutex); |
437 | gref->users++; | 466 | priv->users++; |
438 | spin_unlock(&gref_lock); | 467 | mutex_unlock(&gref_mutex); |
439 | } | 468 | } |
440 | 469 | ||
441 | static void gntalloc_vma_close(struct vm_area_struct *vma) | 470 | static void gntalloc_vma_close(struct vm_area_struct *vma) |
442 | { | 471 | { |
443 | struct gntalloc_gref *gref = vma->vm_private_data; | 472 | struct gntalloc_vma_private_data *priv = vma->vm_private_data; |
444 | if (!gref) | 473 | struct gntalloc_gref *gref, *next; |
474 | int i; | ||
475 | |||
476 | if (!priv) | ||
445 | return; | 477 | return; |
446 | 478 | ||
447 | spin_lock(&gref_lock); | 479 | mutex_lock(&gref_mutex); |
448 | gref->users--; | 480 | priv->users--; |
449 | if (gref->users == 0) | 481 | if (priv->users == 0) { |
450 | __del_gref(gref); | 482 | gref = priv->gref; |
451 | spin_unlock(&gref_lock); | 483 | for (i = 0; i < priv->count; i++) { |
484 | gref->users--; | ||
485 | next = list_entry(gref->next_gref.next, | ||
486 | struct gntalloc_gref, next_gref); | ||
487 | if (gref->users == 0) | ||
488 | __del_gref(gref); | ||
489 | gref = next; | ||
490 | } | ||
491 | kfree(priv); | ||
492 | } | ||
493 | mutex_unlock(&gref_mutex); | ||
452 | } | 494 | } |
453 | 495 | ||
454 | static struct vm_operations_struct gntalloc_vmops = { | 496 | static struct vm_operations_struct gntalloc_vmops = { |
@@ -459,19 +501,25 @@ static struct vm_operations_struct gntalloc_vmops = { | |||
459 | static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma) | 501 | static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma) |
460 | { | 502 | { |
461 | struct gntalloc_file_private_data *priv = filp->private_data; | 503 | struct gntalloc_file_private_data *priv = filp->private_data; |
504 | struct gntalloc_vma_private_data *vm_priv; | ||
462 | struct gntalloc_gref *gref; | 505 | struct gntalloc_gref *gref; |
463 | int count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; | 506 | int count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; |
464 | int rv, i; | 507 | int rv, i; |
465 | 508 | ||
466 | pr_debug("%s: priv %p, page %lu+%d\n", __func__, | ||
467 | priv, vma->vm_pgoff, count); | ||
468 | |||
469 | if (!(vma->vm_flags & VM_SHARED)) { | 509 | if (!(vma->vm_flags & VM_SHARED)) { |
470 | printk(KERN_ERR "%s: Mapping must be shared.\n", __func__); | 510 | printk(KERN_ERR "%s: Mapping must be shared.\n", __func__); |
471 | return -EINVAL; | 511 | return -EINVAL; |
472 | } | 512 | } |
473 | 513 | ||
474 | spin_lock(&gref_lock); | 514 | vm_priv = kmalloc(sizeof(*vm_priv), GFP_KERNEL); |
515 | if (!vm_priv) | ||
516 | return -ENOMEM; | ||
517 | |||
518 | mutex_lock(&gref_mutex); | ||
519 | |||
520 | pr_debug("%s: priv %p,%p, page %lu+%d\n", __func__, | ||
521 | priv, vm_priv, vma->vm_pgoff, count); | ||
522 | |||
475 | gref = find_grefs(priv, vma->vm_pgoff << PAGE_SHIFT, count); | 523 | gref = find_grefs(priv, vma->vm_pgoff << PAGE_SHIFT, count); |
476 | if (gref == NULL) { | 524 | if (gref == NULL) { |
477 | rv = -ENOENT; | 525 | rv = -ENOENT; |
@@ -480,9 +528,13 @@ static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma) | |||
480 | goto out_unlock; | 528 | goto out_unlock; |
481 | } | 529 | } |
482 | 530 | ||
483 | vma->vm_private_data = gref; | 531 | vm_priv->gref = gref; |
532 | vm_priv->users = 1; | ||
533 | vm_priv->count = count; | ||
534 | |||
535 | vma->vm_private_data = vm_priv; | ||
484 | 536 | ||
485 | vma->vm_flags |= VM_RESERVED; | 537 | vma->vm_flags |= VM_RESERVED | VM_DONTEXPAND; |
486 | 538 | ||
487 | vma->vm_ops = &gntalloc_vmops; | 539 | vma->vm_ops = &gntalloc_vmops; |
488 | 540 | ||
@@ -499,7 +551,7 @@ static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma) | |||
499 | rv = 0; | 551 | rv = 0; |
500 | 552 | ||
501 | out_unlock: | 553 | out_unlock: |
502 | spin_unlock(&gref_lock); | 554 | mutex_unlock(&gref_mutex); |
503 | return rv; | 555 | return rv; |
504 | } | 556 | } |
505 | 557 | ||