diff options
author | Eric Anholt <eric@anholt.net> | 2009-03-09 16:42:30 -0400 |
---|---|---|
committer | Eric Anholt <eric@anholt.net> | 2009-03-27 17:47:13 -0400 |
commit | 40123c1f8dd920dcff7a42cde5b351d7d0b0422e (patch) | |
tree | e088817fd6239fe280d53fdb1907864bdf69ca7e | |
parent | 856fa1988ea483fc2dab84a16681dcfde821b740 (diff) |
drm/i915: Fix lock order reversal in shmem pwrite path.
Like the GTT pwrite path fix, this uses an optimistic path and a
fallback to get_user_pages. Note that this means we have to stop using
vfs_write and roll it ourselves.
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-by: Jesse Barnes <jbarnes@virtuousgeek.org>
-rw-r--r-- | drivers/gpu/drm/i915/i915_gem.c | 225 |
1 files changed, 205 insertions, 20 deletions
diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index b998d659fd98..bdc7326052df 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c | |||
@@ -136,6 +136,33 @@ i915_gem_create_ioctl(struct drm_device *dev, void *data, | |||
136 | return 0; | 136 | return 0; |
137 | } | 137 | } |
138 | 138 | ||
139 | static inline int | ||
140 | slow_shmem_copy(struct page *dst_page, | ||
141 | int dst_offset, | ||
142 | struct page *src_page, | ||
143 | int src_offset, | ||
144 | int length) | ||
145 | { | ||
146 | char *dst_vaddr, *src_vaddr; | ||
147 | |||
148 | dst_vaddr = kmap_atomic(dst_page, KM_USER0); | ||
149 | if (dst_vaddr == NULL) | ||
150 | return -ENOMEM; | ||
151 | |||
152 | src_vaddr = kmap_atomic(src_page, KM_USER1); | ||
153 | if (src_vaddr == NULL) { | ||
154 | kunmap_atomic(dst_vaddr, KM_USER0); | ||
155 | return -ENOMEM; | ||
156 | } | ||
157 | |||
158 | memcpy(dst_vaddr + dst_offset, src_vaddr + src_offset, length); | ||
159 | |||
160 | kunmap_atomic(src_vaddr, KM_USER1); | ||
161 | kunmap_atomic(dst_vaddr, KM_USER0); | ||
162 | |||
163 | return 0; | ||
164 | } | ||
165 | |||
139 | /** | 166 | /** |
140 | * Reads data from the object referenced by handle. | 167 | * Reads data from the object referenced by handle. |
141 | * | 168 | * |
@@ -243,6 +270,23 @@ slow_kernel_write(struct io_mapping *mapping, | |||
243 | return 0; | 270 | return 0; |
244 | } | 271 | } |
245 | 272 | ||
273 | static inline int | ||
274 | fast_shmem_write(struct page **pages, | ||
275 | loff_t page_base, int page_offset, | ||
276 | char __user *data, | ||
277 | int length) | ||
278 | { | ||
279 | char __iomem *vaddr; | ||
280 | |||
281 | vaddr = kmap_atomic(pages[page_base >> PAGE_SHIFT], KM_USER0); | ||
282 | if (vaddr == NULL) | ||
283 | return -ENOMEM; | ||
284 | __copy_from_user_inatomic(vaddr + page_offset, data, length); | ||
285 | kunmap_atomic(vaddr, KM_USER0); | ||
286 | |||
287 | return 0; | ||
288 | } | ||
289 | |||
246 | /** | 290 | /** |
247 | * This is the fast pwrite path, where we copy the data directly from the | 291 | * This is the fast pwrite path, where we copy the data directly from the |
248 | * user into the GTT, uncached. | 292 | * user into the GTT, uncached. |
@@ -423,39 +467,175 @@ out_unpin_pages: | |||
423 | return ret; | 467 | return ret; |
424 | } | 468 | } |
425 | 469 | ||
470 | /** | ||
471 | * This is the fast shmem pwrite path, which attempts to directly | ||
472 | * copy_from_user into the kmapped pages backing the object. | ||
473 | */ | ||
426 | static int | 474 | static int |
427 | i915_gem_shmem_pwrite(struct drm_device *dev, struct drm_gem_object *obj, | 475 | i915_gem_shmem_pwrite_fast(struct drm_device *dev, struct drm_gem_object *obj, |
428 | struct drm_i915_gem_pwrite *args, | 476 | struct drm_i915_gem_pwrite *args, |
429 | struct drm_file *file_priv) | 477 | struct drm_file *file_priv) |
430 | { | 478 | { |
479 | struct drm_i915_gem_object *obj_priv = obj->driver_private; | ||
480 | ssize_t remain; | ||
481 | loff_t offset, page_base; | ||
482 | char __user *user_data; | ||
483 | int page_offset, page_length; | ||
431 | int ret; | 484 | int ret; |
432 | loff_t offset; | 485 | |
433 | ssize_t written; | 486 | user_data = (char __user *) (uintptr_t) args->data_ptr; |
487 | remain = args->size; | ||
434 | 488 | ||
435 | mutex_lock(&dev->struct_mutex); | 489 | mutex_lock(&dev->struct_mutex); |
436 | 490 | ||
491 | ret = i915_gem_object_get_pages(obj); | ||
492 | if (ret != 0) | ||
493 | goto fail_unlock; | ||
494 | |||
437 | ret = i915_gem_object_set_to_cpu_domain(obj, 1); | 495 | ret = i915_gem_object_set_to_cpu_domain(obj, 1); |
438 | if (ret) { | 496 | if (ret != 0) |
439 | mutex_unlock(&dev->struct_mutex); | 497 | goto fail_put_pages; |
440 | return ret; | 498 | |
499 | obj_priv = obj->driver_private; | ||
500 | offset = args->offset; | ||
501 | obj_priv->dirty = 1; | ||
502 | |||
503 | while (remain > 0) { | ||
504 | /* Operation in this page | ||
505 | * | ||
506 | * page_base = page offset within aperture | ||
507 | * page_offset = offset within page | ||
508 | * page_length = bytes to copy for this page | ||
509 | */ | ||
510 | page_base = (offset & ~(PAGE_SIZE-1)); | ||
511 | page_offset = offset & (PAGE_SIZE-1); | ||
512 | page_length = remain; | ||
513 | if ((page_offset + remain) > PAGE_SIZE) | ||
514 | page_length = PAGE_SIZE - page_offset; | ||
515 | |||
516 | ret = fast_shmem_write(obj_priv->pages, | ||
517 | page_base, page_offset, | ||
518 | user_data, page_length); | ||
519 | if (ret) | ||
520 | goto fail_put_pages; | ||
521 | |||
522 | remain -= page_length; | ||
523 | user_data += page_length; | ||
524 | offset += page_length; | ||
441 | } | 525 | } |
442 | 526 | ||
527 | fail_put_pages: | ||
528 | i915_gem_object_put_pages(obj); | ||
529 | fail_unlock: | ||
530 | mutex_unlock(&dev->struct_mutex); | ||
531 | |||
532 | return ret; | ||
533 | } | ||
534 | |||
535 | /** | ||
536 | * This is the fallback shmem pwrite path, which uses get_user_pages to pin | ||
537 | * the memory and maps it using kmap_atomic for copying. | ||
538 | * | ||
539 | * This avoids taking mmap_sem for faulting on the user's address while the | ||
540 | * struct_mutex is held. | ||
541 | */ | ||
542 | static int | ||
543 | i915_gem_shmem_pwrite_slow(struct drm_device *dev, struct drm_gem_object *obj, | ||
544 | struct drm_i915_gem_pwrite *args, | ||
545 | struct drm_file *file_priv) | ||
546 | { | ||
547 | struct drm_i915_gem_object *obj_priv = obj->driver_private; | ||
548 | struct mm_struct *mm = current->mm; | ||
549 | struct page **user_pages; | ||
550 | ssize_t remain; | ||
551 | loff_t offset, pinned_pages, i; | ||
552 | loff_t first_data_page, last_data_page, num_pages; | ||
553 | int shmem_page_index, shmem_page_offset; | ||
554 | int data_page_index, data_page_offset; | ||
555 | int page_length; | ||
556 | int ret; | ||
557 | uint64_t data_ptr = args->data_ptr; | ||
558 | |||
559 | remain = args->size; | ||
560 | |||
561 | /* Pin the user pages containing the data. We can't fault while | ||
562 | * holding the struct mutex, and all of the pwrite implementations | ||
563 | * want to hold it while dereferencing the user data. | ||
564 | */ | ||
565 | first_data_page = data_ptr / PAGE_SIZE; | ||
566 | last_data_page = (data_ptr + args->size - 1) / PAGE_SIZE; | ||
567 | num_pages = last_data_page - first_data_page + 1; | ||
568 | |||
569 | user_pages = kcalloc(num_pages, sizeof(struct page *), GFP_KERNEL); | ||
570 | if (user_pages == NULL) | ||
571 | return -ENOMEM; | ||
572 | |||
573 | down_read(&mm->mmap_sem); | ||
574 | pinned_pages = get_user_pages(current, mm, (uintptr_t)args->data_ptr, | ||
575 | num_pages, 0, 0, user_pages, NULL); | ||
576 | up_read(&mm->mmap_sem); | ||
577 | if (pinned_pages < num_pages) { | ||
578 | ret = -EFAULT; | ||
579 | goto fail_put_user_pages; | ||
580 | } | ||
581 | |||
582 | mutex_lock(&dev->struct_mutex); | ||
583 | |||
584 | ret = i915_gem_object_get_pages(obj); | ||
585 | if (ret != 0) | ||
586 | goto fail_unlock; | ||
587 | |||
588 | ret = i915_gem_object_set_to_cpu_domain(obj, 1); | ||
589 | if (ret != 0) | ||
590 | goto fail_put_pages; | ||
591 | |||
592 | obj_priv = obj->driver_private; | ||
443 | offset = args->offset; | 593 | offset = args->offset; |
594 | obj_priv->dirty = 1; | ||
444 | 595 | ||
445 | written = vfs_write(obj->filp, | 596 | while (remain > 0) { |
446 | (char __user *)(uintptr_t) args->data_ptr, | 597 | /* Operation in this page |
447 | args->size, &offset); | 598 | * |
448 | if (written != args->size) { | 599 | * shmem_page_index = page number within shmem file |
449 | mutex_unlock(&dev->struct_mutex); | 600 | * shmem_page_offset = offset within page in shmem file |
450 | if (written < 0) | 601 | * data_page_index = page number in get_user_pages return |
451 | return written; | 602 | * data_page_offset = offset with data_page_index page. |
452 | else | 603 | * page_length = bytes to copy for this page |
453 | return -EINVAL; | 604 | */ |
605 | shmem_page_index = offset / PAGE_SIZE; | ||
606 | shmem_page_offset = offset & ~PAGE_MASK; | ||
607 | data_page_index = data_ptr / PAGE_SIZE - first_data_page; | ||
608 | data_page_offset = data_ptr & ~PAGE_MASK; | ||
609 | |||
610 | page_length = remain; | ||
611 | if ((shmem_page_offset + page_length) > PAGE_SIZE) | ||
612 | page_length = PAGE_SIZE - shmem_page_offset; | ||
613 | if ((data_page_offset + page_length) > PAGE_SIZE) | ||
614 | page_length = PAGE_SIZE - data_page_offset; | ||
615 | |||
616 | ret = slow_shmem_copy(obj_priv->pages[shmem_page_index], | ||
617 | shmem_page_offset, | ||
618 | user_pages[data_page_index], | ||
619 | data_page_offset, | ||
620 | page_length); | ||
621 | if (ret) | ||
622 | goto fail_put_pages; | ||
623 | |||
624 | remain -= page_length; | ||
625 | data_ptr += page_length; | ||
626 | offset += page_length; | ||
454 | } | 627 | } |
455 | 628 | ||
629 | fail_put_pages: | ||
630 | i915_gem_object_put_pages(obj); | ||
631 | fail_unlock: | ||
456 | mutex_unlock(&dev->struct_mutex); | 632 | mutex_unlock(&dev->struct_mutex); |
633 | fail_put_user_pages: | ||
634 | for (i = 0; i < pinned_pages; i++) | ||
635 | page_cache_release(user_pages[i]); | ||
636 | kfree(user_pages); | ||
457 | 637 | ||
458 | return 0; | 638 | return ret; |
459 | } | 639 | } |
460 | 640 | ||
461 | /** | 641 | /** |
@@ -502,8 +682,13 @@ i915_gem_pwrite_ioctl(struct drm_device *dev, void *data, | |||
502 | ret = i915_gem_gtt_pwrite_slow(dev, obj, args, | 682 | ret = i915_gem_gtt_pwrite_slow(dev, obj, args, |
503 | file_priv); | 683 | file_priv); |
504 | } | 684 | } |
505 | } else | 685 | } else { |
506 | ret = i915_gem_shmem_pwrite(dev, obj, args, file_priv); | 686 | ret = i915_gem_shmem_pwrite_fast(dev, obj, args, file_priv); |
687 | if (ret == -EFAULT) { | ||
688 | ret = i915_gem_shmem_pwrite_slow(dev, obj, args, | ||
689 | file_priv); | ||
690 | } | ||
691 | } | ||
507 | 692 | ||
508 | #if WATCH_PWRITE | 693 | #if WATCH_PWRITE |
509 | if (ret) | 694 | if (ret) |