Rodrigo Siqueira | 559e50f | 2018-07-11 23:01:47 -0300 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * This program is free software; you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License as published by |
| 5 | * the Free Software Foundation; either version 2 of the License, or |
| 6 | * (at your option) any later version. |
| 7 | */ |
| 8 | |
| 9 | #include <linux/shmem_fs.h> |
| 10 | |
| 11 | #include "vkms_drv.h" |
| 12 | |
| 13 | static struct vkms_gem_object *__vkms_gem_create(struct drm_device *dev, |
| 14 | u64 size) |
| 15 | { |
| 16 | struct vkms_gem_object *obj; |
| 17 | int ret; |
| 18 | |
| 19 | obj = kzalloc(sizeof(*obj), GFP_KERNEL); |
| 20 | if (!obj) |
| 21 | return ERR_PTR(-ENOMEM); |
| 22 | |
| 23 | size = roundup(size, PAGE_SIZE); |
| 24 | ret = drm_gem_object_init(dev, &obj->gem, size); |
| 25 | if (ret) { |
| 26 | kfree(obj); |
| 27 | return ERR_PTR(ret); |
| 28 | } |
| 29 | |
| 30 | mutex_init(&obj->pages_lock); |
| 31 | |
| 32 | return obj; |
| 33 | } |
| 34 | |
| 35 | void vkms_gem_free_object(struct drm_gem_object *obj) |
| 36 | { |
| 37 | struct vkms_gem_object *gem = container_of(obj, struct vkms_gem_object, |
| 38 | gem); |
| 39 | |
Haneen Mohammed | bb112b1 | 2018-07-24 19:26:59 +0300 | [diff] [blame] | 40 | WARN_ON(gem->pages); |
| 41 | WARN_ON(gem->vaddr); |
| 42 | |
Rodrigo Siqueira | 559e50f | 2018-07-11 23:01:47 -0300 | [diff] [blame] | 43 | mutex_destroy(&gem->pages_lock); |
| 44 | drm_gem_object_release(obj); |
| 45 | kfree(gem); |
| 46 | } |
| 47 | |
Souptick Joarder | 7c3d0f1 | 2018-07-26 20:15:49 +0530 | [diff] [blame] | 48 | vm_fault_t vkms_gem_fault(struct vm_fault *vmf) |
Rodrigo Siqueira | 559e50f | 2018-07-11 23:01:47 -0300 | [diff] [blame] | 49 | { |
| 50 | struct vm_area_struct *vma = vmf->vma; |
| 51 | struct vkms_gem_object *obj = vma->vm_private_data; |
| 52 | unsigned long vaddr = vmf->address; |
| 53 | pgoff_t page_offset; |
| 54 | loff_t num_pages; |
Souptick Joarder | 7c3d0f1 | 2018-07-26 20:15:49 +0530 | [diff] [blame] | 55 | vm_fault_t ret = VM_FAULT_SIGBUS; |
Rodrigo Siqueira | 559e50f | 2018-07-11 23:01:47 -0300 | [diff] [blame] | 56 | |
| 57 | page_offset = (vaddr - vma->vm_start) >> PAGE_SHIFT; |
| 58 | num_pages = DIV_ROUND_UP(obj->gem.size, PAGE_SIZE); |
| 59 | |
| 60 | if (page_offset > num_pages) |
| 61 | return VM_FAULT_SIGBUS; |
| 62 | |
Rodrigo Siqueira | 559e50f | 2018-07-11 23:01:47 -0300 | [diff] [blame] | 63 | mutex_lock(&obj->pages_lock); |
| 64 | if (obj->pages) { |
| 65 | get_page(obj->pages[page_offset]); |
| 66 | vmf->page = obj->pages[page_offset]; |
| 67 | ret = 0; |
| 68 | } |
| 69 | mutex_unlock(&obj->pages_lock); |
| 70 | if (ret) { |
| 71 | struct page *page; |
| 72 | struct address_space *mapping; |
| 73 | |
| 74 | mapping = file_inode(obj->gem.filp)->i_mapping; |
| 75 | page = shmem_read_mapping_page(mapping, page_offset); |
| 76 | |
| 77 | if (!IS_ERR(page)) { |
| 78 | vmf->page = page; |
| 79 | ret = 0; |
| 80 | } else { |
| 81 | switch (PTR_ERR(page)) { |
| 82 | case -ENOSPC: |
| 83 | case -ENOMEM: |
| 84 | ret = VM_FAULT_OOM; |
| 85 | break; |
| 86 | case -EBUSY: |
| 87 | ret = VM_FAULT_RETRY; |
| 88 | break; |
| 89 | case -EFAULT: |
| 90 | case -EINVAL: |
| 91 | ret = VM_FAULT_SIGBUS; |
| 92 | break; |
| 93 | default: |
| 94 | WARN_ON(PTR_ERR(page)); |
| 95 | ret = VM_FAULT_SIGBUS; |
| 96 | break; |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | return ret; |
| 101 | } |
| 102 | |
| 103 | struct drm_gem_object *vkms_gem_create(struct drm_device *dev, |
| 104 | struct drm_file *file, |
| 105 | u32 *handle, |
| 106 | u64 size) |
| 107 | { |
| 108 | struct vkms_gem_object *obj; |
| 109 | int ret; |
| 110 | |
| 111 | if (!file || !dev || !handle) |
| 112 | return ERR_PTR(-EINVAL); |
| 113 | |
| 114 | obj = __vkms_gem_create(dev, size); |
| 115 | if (IS_ERR(obj)) |
| 116 | return ERR_CAST(obj); |
| 117 | |
| 118 | ret = drm_gem_handle_create(file, &obj->gem, handle); |
| 119 | drm_gem_object_put_unlocked(&obj->gem); |
| 120 | if (ret) { |
| 121 | drm_gem_object_release(&obj->gem); |
| 122 | kfree(obj); |
| 123 | return ERR_PTR(ret); |
| 124 | } |
| 125 | |
| 126 | return &obj->gem; |
| 127 | } |
| 128 | |
| 129 | int vkms_dumb_create(struct drm_file *file, struct drm_device *dev, |
| 130 | struct drm_mode_create_dumb *args) |
| 131 | { |
| 132 | struct drm_gem_object *gem_obj; |
| 133 | u64 pitch, size; |
| 134 | |
| 135 | if (!args || !dev || !file) |
| 136 | return -EINVAL; |
| 137 | |
| 138 | pitch = args->width * DIV_ROUND_UP(args->bpp, 8); |
| 139 | size = pitch * args->height; |
| 140 | |
| 141 | if (!size) |
| 142 | return -EINVAL; |
| 143 | |
| 144 | gem_obj = vkms_gem_create(dev, file, &args->handle, size); |
| 145 | if (IS_ERR(gem_obj)) |
| 146 | return PTR_ERR(gem_obj); |
| 147 | |
| 148 | args->size = gem_obj->size; |
| 149 | args->pitch = pitch; |
| 150 | |
| 151 | DRM_DEBUG_DRIVER("Created object of size %lld\n", size); |
| 152 | |
| 153 | return 0; |
| 154 | } |
| 155 | |
| 156 | int vkms_dumb_map(struct drm_file *file, struct drm_device *dev, |
| 157 | u32 handle, u64 *offset) |
| 158 | { |
| 159 | struct drm_gem_object *obj; |
| 160 | int ret; |
| 161 | |
| 162 | obj = drm_gem_object_lookup(file, handle); |
| 163 | if (!obj) |
| 164 | return -ENOENT; |
| 165 | |
| 166 | if (!obj->filp) { |
| 167 | ret = -EINVAL; |
| 168 | goto unref; |
| 169 | } |
| 170 | |
| 171 | ret = drm_gem_create_mmap_offset(obj); |
| 172 | if (ret) |
| 173 | goto unref; |
| 174 | |
| 175 | *offset = drm_vma_node_offset_addr(&obj->vma_node); |
| 176 | unref: |
| 177 | drm_gem_object_put_unlocked(obj); |
| 178 | |
| 179 | return ret; |
| 180 | } |
Haneen Mohammed | bb112b1 | 2018-07-24 19:26:59 +0300 | [diff] [blame] | 181 | |
| 182 | static struct page **_get_pages(struct vkms_gem_object *vkms_obj) |
| 183 | { |
| 184 | struct drm_gem_object *gem_obj = &vkms_obj->gem; |
| 185 | |
| 186 | if (!vkms_obj->pages) { |
| 187 | struct page **pages = drm_gem_get_pages(gem_obj); |
| 188 | |
| 189 | if (IS_ERR(pages)) |
| 190 | return pages; |
| 191 | |
| 192 | if (cmpxchg(&vkms_obj->pages, NULL, pages)) |
| 193 | drm_gem_put_pages(gem_obj, pages, false, true); |
| 194 | } |
| 195 | |
| 196 | return vkms_obj->pages; |
| 197 | } |
| 198 | |
| 199 | void vkms_gem_vunmap(struct drm_gem_object *obj) |
| 200 | { |
| 201 | struct vkms_gem_object *vkms_obj = drm_gem_to_vkms_gem(obj); |
| 202 | |
| 203 | mutex_lock(&vkms_obj->pages_lock); |
| 204 | if (vkms_obj->vmap_count < 1) { |
| 205 | WARN_ON(vkms_obj->vaddr); |
| 206 | WARN_ON(vkms_obj->pages); |
| 207 | mutex_unlock(&vkms_obj->pages_lock); |
| 208 | return; |
| 209 | } |
| 210 | |
| 211 | vkms_obj->vmap_count--; |
| 212 | |
| 213 | if (vkms_obj->vmap_count == 0) { |
| 214 | vunmap(vkms_obj->vaddr); |
| 215 | vkms_obj->vaddr = NULL; |
| 216 | drm_gem_put_pages(obj, vkms_obj->pages, false, true); |
| 217 | vkms_obj->pages = NULL; |
| 218 | } |
| 219 | |
| 220 | mutex_unlock(&vkms_obj->pages_lock); |
| 221 | } |
| 222 | |
| 223 | int vkms_gem_vmap(struct drm_gem_object *obj) |
| 224 | { |
| 225 | struct vkms_gem_object *vkms_obj = drm_gem_to_vkms_gem(obj); |
| 226 | int ret = 0; |
| 227 | |
| 228 | mutex_lock(&vkms_obj->pages_lock); |
| 229 | |
| 230 | if (!vkms_obj->vaddr) { |
| 231 | unsigned int n_pages = obj->size >> PAGE_SHIFT; |
| 232 | struct page **pages = _get_pages(vkms_obj); |
| 233 | |
| 234 | if (IS_ERR(pages)) { |
| 235 | ret = PTR_ERR(pages); |
| 236 | goto out; |
| 237 | } |
| 238 | |
| 239 | vkms_obj->vaddr = vmap(pages, n_pages, VM_MAP, PAGE_KERNEL); |
| 240 | if (!vkms_obj->vaddr) |
| 241 | goto err_vmap; |
Haneen Mohammed | bb112b1 | 2018-07-24 19:26:59 +0300 | [diff] [blame] | 242 | } |
| 243 | |
Haneen Mohammed | 31e63d3 | 2018-08-01 12:08:07 +0300 | [diff] [blame^] | 244 | vkms_obj->vmap_count++; |
Haneen Mohammed | bb112b1 | 2018-07-24 19:26:59 +0300 | [diff] [blame] | 245 | goto out; |
| 246 | |
| 247 | err_vmap: |
| 248 | ret = -ENOMEM; |
| 249 | drm_gem_put_pages(obj, vkms_obj->pages, false, true); |
| 250 | vkms_obj->pages = NULL; |
| 251 | out: |
| 252 | mutex_unlock(&vkms_obj->pages_lock); |
| 253 | return ret; |
| 254 | } |