aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/i915/i915_gem.c
diff options
context:
space:
mode:
authorDaniel Vetter <daniel.vetter@ffwll.ch>2011-12-14 07:57:31 -0500
committerDaniel Vetter <daniel.vetter@ffwll.ch>2012-01-30 17:34:21 -0500
commit8c59967c448af5d77fe5189d0e73f32cd3dbe439 (patch)
tree18b3a88249e8f87a517df421c17d0eca5c3bfeb7 /drivers/gpu/drm/i915/i915_gem.c
parent5c0480f21f9896c443b0e65d779c8e09a695da7b (diff)
drm/i915: rewrite shmem_pwrite_slow to use copy_from_user
... instead of get_user_pages, because that fails on non page-backed user addresses like e.g. a gtt mapping of a bo. To get there essentially copy the vfs read path into pagecache. We can't call that right away because we have to take care of bit17 swizzling. To not deadlock with our own pagefault handler we need to completely drop struct_mutex, reducing the atomicty-guarantees of our userspace abi. Implications for racing with other gem ioctl: - execbuf, pwrite, pread: Due to -EFAULT fallback to slow paths there's already the risk of the pwrite call not being atomic, no degration. - read/write access to mmaps: already fully racy, no degration. - set_tiling: Calling set_tiling while reading/writing is already pretty much undefined, now it just got a bit worse. set_tiling is only called by libdrm on unused/new bos, so no problem. - set_domain: When changing to the gtt domain while copying (without any read/write access, e.g. for synchronization), we might leave unflushed data in the cpu caches. The clflush_object at the end of pwrite_slow takes care of this problem. - truncating of purgeable objects: the shmem_read_mapping_page call could reinstate backing storage for truncated objects. The check at the end of pwrite_slow takes care of this. v2: - add missing intel_gtt_chipset_flush - add __ to copy_from_user_swizzled as suggest by Chris Wilson. v3: Fixup bit17 swizzling, it swizzled the wrong pages. Reviewed-by: Chris Wilson <chris@chris-wilson.co.uk> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Diffstat (limited to 'drivers/gpu/drm/i915/i915_gem.c')
-rw-r--r--drivers/gpu/drm/i915/i915_gem.c127
1 files changed, 65 insertions, 62 deletions
diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c
index 7bb32ecc13c5..3f391aa76d31 100644
--- a/drivers/gpu/drm/i915/i915_gem.c
+++ b/drivers/gpu/drm/i915/i915_gem.c
@@ -58,6 +58,7 @@ static void i915_gem_free_object_tail(struct drm_i915_gem_object *obj);
58 58
59static int i915_gem_inactive_shrink(struct shrinker *shrinker, 59static int i915_gem_inactive_shrink(struct shrinker *shrinker,
60 struct shrink_control *sc); 60 struct shrink_control *sc);
61static void i915_gem_object_truncate(struct drm_i915_gem_object *obj);
61 62
62/* some bookkeeping */ 63/* some bookkeeping */
63static void i915_gem_info_add_obj(struct drm_i915_private *dev_priv, 64static void i915_gem_info_add_obj(struct drm_i915_private *dev_priv,
@@ -385,6 +386,32 @@ i915_gem_shmem_pread_fast(struct drm_device *dev,
385 return 0; 386 return 0;
386} 387}
387 388
389static inline int
390__copy_from_user_swizzled(char __user *gpu_vaddr, int gpu_offset,
391 const char *cpu_vaddr,
392 int length)
393{
394 int ret, cpu_offset = 0;
395
396 while (length > 0) {
397 int cacheline_end = ALIGN(gpu_offset + 1, 64);
398 int this_length = min(cacheline_end - gpu_offset, length);
399 int swizzled_gpu_offset = gpu_offset ^ 64;
400
401 ret = __copy_from_user(gpu_vaddr + swizzled_gpu_offset,
402 cpu_vaddr + cpu_offset,
403 this_length);
404 if (ret)
405 return ret + length;
406
407 cpu_offset += this_length;
408 gpu_offset += this_length;
409 length -= this_length;
410 }
411
412 return 0;
413}
414
388/** 415/**
389 * This is the fallback shmem pread path, which allocates temporary storage 416 * This is the fallback shmem pread path, which allocates temporary storage
390 * in kernel space to copy_to_user into outside of the struct_mutex, so we 417 * in kernel space to copy_to_user into outside of the struct_mutex, so we
@@ -841,71 +868,36 @@ i915_gem_shmem_pwrite_slow(struct drm_device *dev,
841 struct drm_file *file) 868 struct drm_file *file)
842{ 869{
843 struct address_space *mapping = obj->base.filp->f_path.dentry->d_inode->i_mapping; 870 struct address_space *mapping = obj->base.filp->f_path.dentry->d_inode->i_mapping;
844 struct mm_struct *mm = current->mm;
845 struct page **user_pages;
846 ssize_t remain; 871 ssize_t remain;
847 loff_t offset, pinned_pages, i; 872 loff_t offset;
848 loff_t first_data_page, last_data_page, num_pages; 873 char __user *user_data;
849 int shmem_page_offset; 874 int shmem_page_offset, page_length, ret;
850 int data_page_index, data_page_offset; 875 int obj_do_bit17_swizzling, page_do_bit17_swizzling;
851 int page_length;
852 int ret;
853 uint64_t data_ptr = args->data_ptr;
854 int do_bit17_swizzling;
855 876
877 user_data = (char __user *) (uintptr_t) args->data_ptr;
856 remain = args->size; 878 remain = args->size;
857 879
858 /* Pin the user pages containing the data. We can't fault while 880 obj_do_bit17_swizzling = i915_gem_object_needs_bit17_swizzle(obj);
859 * holding the struct mutex, and all of the pwrite implementations
860 * want to hold it while dereferencing the user data.
861 */
862 first_data_page = data_ptr / PAGE_SIZE;
863 last_data_page = (data_ptr + args->size - 1) / PAGE_SIZE;
864 num_pages = last_data_page - first_data_page + 1;
865
866 user_pages = drm_malloc_ab(num_pages, sizeof(struct page *));
867 if (user_pages == NULL)
868 return -ENOMEM;
869
870 mutex_unlock(&dev->struct_mutex);
871 down_read(&mm->mmap_sem);
872 pinned_pages = get_user_pages(current, mm, (uintptr_t)args->data_ptr,
873 num_pages, 0, 0, user_pages, NULL);
874 up_read(&mm->mmap_sem);
875 mutex_lock(&dev->struct_mutex);
876 if (pinned_pages < num_pages) {
877 ret = -EFAULT;
878 goto out;
879 }
880
881 ret = i915_gem_object_set_to_cpu_domain(obj, 1);
882 if (ret)
883 goto out;
884
885 do_bit17_swizzling = i915_gem_object_needs_bit17_swizzle(obj);
886 881
887 offset = args->offset; 882 offset = args->offset;
888 obj->dirty = 1; 883 obj->dirty = 1;
889 884
885 mutex_unlock(&dev->struct_mutex);
886
890 while (remain > 0) { 887 while (remain > 0) {
891 struct page *page; 888 struct page *page;
889 char *vaddr;
892 890
893 /* Operation in this page 891 /* Operation in this page
894 * 892 *
895 * shmem_page_offset = offset within page in shmem file 893 * shmem_page_offset = offset within page in shmem file
896 * data_page_index = page number in get_user_pages return
897 * data_page_offset = offset with data_page_index page.
898 * page_length = bytes to copy for this page 894 * page_length = bytes to copy for this page
899 */ 895 */
900 shmem_page_offset = offset_in_page(offset); 896 shmem_page_offset = offset_in_page(offset);
901 data_page_index = data_ptr / PAGE_SIZE - first_data_page;
902 data_page_offset = offset_in_page(data_ptr);
903 897
904 page_length = remain; 898 page_length = remain;
905 if ((shmem_page_offset + page_length) > PAGE_SIZE) 899 if ((shmem_page_offset + page_length) > PAGE_SIZE)
906 page_length = PAGE_SIZE - shmem_page_offset; 900 page_length = PAGE_SIZE - shmem_page_offset;
907 if ((data_page_offset + page_length) > PAGE_SIZE)
908 page_length = PAGE_SIZE - data_page_offset;
909 901
910 page = shmem_read_mapping_page(mapping, offset >> PAGE_SHIFT); 902 page = shmem_read_mapping_page(mapping, offset >> PAGE_SHIFT);
911 if (IS_ERR(page)) { 903 if (IS_ERR(page)) {
@@ -913,34 +905,45 @@ i915_gem_shmem_pwrite_slow(struct drm_device *dev,
913 goto out; 905 goto out;
914 } 906 }
915 907
916 if (do_bit17_swizzling) { 908 page_do_bit17_swizzling = obj_do_bit17_swizzling &&
917 slow_shmem_bit17_copy(page, 909 (page_to_phys(page) & (1 << 17)) != 0;
918 shmem_page_offset, 910
919 user_pages[data_page_index], 911 vaddr = kmap(page);
920 data_page_offset, 912 if (page_do_bit17_swizzling)
921 page_length, 913 ret = __copy_from_user_swizzled(vaddr, shmem_page_offset,
922 0); 914 user_data,
923 } else { 915 page_length);
924 slow_shmem_copy(page, 916 else
925 shmem_page_offset, 917 ret = __copy_from_user(vaddr + shmem_page_offset,
926 user_pages[data_page_index], 918 user_data,
927 data_page_offset, 919 page_length);
928 page_length); 920 kunmap(page);
929 }
930 921
931 set_page_dirty(page); 922 set_page_dirty(page);
932 mark_page_accessed(page); 923 mark_page_accessed(page);
933 page_cache_release(page); 924 page_cache_release(page);
934 925
926 if (ret) {
927 ret = -EFAULT;
928 goto out;
929 }
930
935 remain -= page_length; 931 remain -= page_length;
936 data_ptr += page_length; 932 user_data += page_length;
937 offset += page_length; 933 offset += page_length;
938 } 934 }
939 935
940out: 936out:
941 for (i = 0; i < pinned_pages; i++) 937 mutex_lock(&dev->struct_mutex);
942 page_cache_release(user_pages[i]); 938 /* Fixup: Kill any reinstated backing storage pages */
943 drm_free_large(user_pages); 939 if (obj->madv == __I915_MADV_PURGED)
940 i915_gem_object_truncate(obj);
941 /* and flush dirty cachelines in case the object isn't in the cpu write
942 * domain anymore. */
943 if (obj->base.write_domain != I915_GEM_DOMAIN_CPU) {
944 i915_gem_clflush_object(obj);
945 intel_gtt_chipset_flush();
946 }
944 947
945 return ret; 948 return ret;
946} 949}