1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* exynos_drm_gem.c |
3 | * |
4 | * Copyright (c) 2011 Samsung Electronics Co., Ltd. |
5 | * Author: Inki Dae <inki.dae@samsung.com> |
6 | */ |
7 | |
8 | |
9 | #include <linux/dma-buf.h> |
10 | #include <linux/pfn_t.h> |
11 | #include <linux/shmem_fs.h> |
12 | #include <linux/module.h> |
13 | |
14 | #include <drm/drm_prime.h> |
15 | #include <drm/drm_vma_manager.h> |
16 | #include <drm/exynos_drm.h> |
17 | |
18 | #include "exynos_drm_drv.h" |
19 | #include "exynos_drm_gem.h" |
20 | |
21 | MODULE_IMPORT_NS(DMA_BUF); |
22 | |
23 | static int exynos_drm_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma); |
24 | |
25 | static int exynos_drm_alloc_buf(struct exynos_drm_gem *exynos_gem, bool kvmap) |
26 | { |
27 | struct drm_device *dev = exynos_gem->base.dev; |
28 | unsigned long attr = 0; |
29 | |
30 | if (exynos_gem->dma_addr) { |
31 | DRM_DEV_DEBUG_KMS(to_dma_dev(dev), "already allocated.\n" ); |
32 | return 0; |
33 | } |
34 | |
35 | /* |
36 | * if EXYNOS_BO_CONTIG, fully physically contiguous memory |
37 | * region will be allocated else physically contiguous |
38 | * as possible. |
39 | */ |
40 | if (!(exynos_gem->flags & EXYNOS_BO_NONCONTIG)) |
41 | attr |= DMA_ATTR_FORCE_CONTIGUOUS; |
42 | |
43 | /* |
44 | * if EXYNOS_BO_WC or EXYNOS_BO_NONCACHABLE, writecombine mapping |
45 | * else cachable mapping. |
46 | */ |
47 | if (exynos_gem->flags & EXYNOS_BO_WC || |
48 | !(exynos_gem->flags & EXYNOS_BO_CACHABLE)) |
49 | attr |= DMA_ATTR_WRITE_COMBINE; |
50 | |
51 | /* FBDev emulation requires kernel mapping */ |
52 | if (!kvmap) |
53 | attr |= DMA_ATTR_NO_KERNEL_MAPPING; |
54 | |
55 | exynos_gem->dma_attrs = attr; |
56 | exynos_gem->cookie = dma_alloc_attrs(dev: to_dma_dev(dev), size: exynos_gem->size, |
57 | dma_handle: &exynos_gem->dma_addr, GFP_KERNEL, |
58 | attrs: exynos_gem->dma_attrs); |
59 | if (!exynos_gem->cookie) { |
60 | DRM_DEV_ERROR(to_dma_dev(dev), "failed to allocate buffer.\n" ); |
61 | return -ENOMEM; |
62 | } |
63 | |
64 | if (kvmap) |
65 | exynos_gem->kvaddr = exynos_gem->cookie; |
66 | |
67 | DRM_DEV_DEBUG_KMS(to_dma_dev(dev), "dma_addr(0x%lx), size(0x%lx)\n" , |
68 | (unsigned long)exynos_gem->dma_addr, exynos_gem->size); |
69 | return 0; |
70 | } |
71 | |
72 | static void exynos_drm_free_buf(struct exynos_drm_gem *exynos_gem) |
73 | { |
74 | struct drm_device *dev = exynos_gem->base.dev; |
75 | |
76 | if (!exynos_gem->dma_addr) { |
77 | DRM_DEV_DEBUG_KMS(dev->dev, "dma_addr is invalid.\n" ); |
78 | return; |
79 | } |
80 | |
81 | DRM_DEV_DEBUG_KMS(dev->dev, "dma_addr(0x%lx), size(0x%lx)\n" , |
82 | (unsigned long)exynos_gem->dma_addr, exynos_gem->size); |
83 | |
84 | dma_free_attrs(dev: to_dma_dev(dev), size: exynos_gem->size, cpu_addr: exynos_gem->cookie, |
85 | dma_handle: (dma_addr_t)exynos_gem->dma_addr, |
86 | attrs: exynos_gem->dma_attrs); |
87 | } |
88 | |
89 | static int exynos_drm_gem_handle_create(struct drm_gem_object *obj, |
90 | struct drm_file *file_priv, |
91 | unsigned int *handle) |
92 | { |
93 | int ret; |
94 | |
95 | /* |
96 | * allocate a id of idr table where the obj is registered |
97 | * and handle has the id what user can see. |
98 | */ |
99 | ret = drm_gem_handle_create(file_priv, obj, handlep: handle); |
100 | if (ret) |
101 | return ret; |
102 | |
103 | DRM_DEV_DEBUG_KMS(to_dma_dev(obj->dev), "gem handle = 0x%x\n" , *handle); |
104 | |
105 | /* drop reference from allocate - handle holds it now. */ |
106 | drm_gem_object_put(obj); |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | void exynos_drm_gem_destroy(struct exynos_drm_gem *exynos_gem) |
112 | { |
113 | struct drm_gem_object *obj = &exynos_gem->base; |
114 | |
115 | DRM_DEV_DEBUG_KMS(to_dma_dev(obj->dev), "handle count = %d\n" , |
116 | obj->handle_count); |
117 | |
118 | /* |
119 | * do not release memory region from exporter. |
120 | * |
121 | * the region will be released by exporter |
122 | * once dmabuf's refcount becomes 0. |
123 | */ |
124 | if (obj->import_attach) |
125 | drm_prime_gem_destroy(obj, sg: exynos_gem->sgt); |
126 | else |
127 | exynos_drm_free_buf(exynos_gem); |
128 | |
129 | /* release file pointer to gem object. */ |
130 | drm_gem_object_release(obj); |
131 | |
132 | kfree(objp: exynos_gem); |
133 | } |
134 | |
135 | static const struct vm_operations_struct exynos_drm_gem_vm_ops = { |
136 | .open = drm_gem_vm_open, |
137 | .close = drm_gem_vm_close, |
138 | }; |
139 | |
140 | static const struct drm_gem_object_funcs exynos_drm_gem_object_funcs = { |
141 | .free = exynos_drm_gem_free_object, |
142 | .get_sg_table = exynos_drm_gem_prime_get_sg_table, |
143 | .mmap = exynos_drm_gem_mmap, |
144 | .vm_ops = &exynos_drm_gem_vm_ops, |
145 | }; |
146 | |
147 | static struct exynos_drm_gem *exynos_drm_gem_init(struct drm_device *dev, |
148 | unsigned long size) |
149 | { |
150 | struct exynos_drm_gem *exynos_gem; |
151 | struct drm_gem_object *obj; |
152 | int ret; |
153 | |
154 | exynos_gem = kzalloc(size: sizeof(*exynos_gem), GFP_KERNEL); |
155 | if (!exynos_gem) |
156 | return ERR_PTR(error: -ENOMEM); |
157 | |
158 | exynos_gem->size = size; |
159 | obj = &exynos_gem->base; |
160 | |
161 | obj->funcs = &exynos_drm_gem_object_funcs; |
162 | |
163 | ret = drm_gem_object_init(dev, obj, size); |
164 | if (ret < 0) { |
165 | DRM_DEV_ERROR(dev->dev, "failed to initialize gem object\n" ); |
166 | kfree(objp: exynos_gem); |
167 | return ERR_PTR(error: ret); |
168 | } |
169 | |
170 | ret = drm_gem_create_mmap_offset(obj); |
171 | if (ret < 0) { |
172 | drm_gem_object_release(obj); |
173 | kfree(objp: exynos_gem); |
174 | return ERR_PTR(error: ret); |
175 | } |
176 | |
177 | DRM_DEV_DEBUG_KMS(dev->dev, "created file object = %pK\n" , obj->filp); |
178 | |
179 | return exynos_gem; |
180 | } |
181 | |
182 | struct exynos_drm_gem *exynos_drm_gem_create(struct drm_device *dev, |
183 | unsigned int flags, |
184 | unsigned long size, |
185 | bool kvmap) |
186 | { |
187 | struct exynos_drm_gem *exynos_gem; |
188 | int ret; |
189 | |
190 | if (flags & ~(EXYNOS_BO_MASK)) { |
191 | DRM_DEV_ERROR(dev->dev, |
192 | "invalid GEM buffer flags: %u\n" , flags); |
193 | return ERR_PTR(error: -EINVAL); |
194 | } |
195 | |
196 | if (!size) { |
197 | DRM_DEV_ERROR(dev->dev, "invalid GEM buffer size: %lu\n" , size); |
198 | return ERR_PTR(error: -EINVAL); |
199 | } |
200 | |
201 | size = roundup(size, PAGE_SIZE); |
202 | |
203 | exynos_gem = exynos_drm_gem_init(dev, size); |
204 | if (IS_ERR(ptr: exynos_gem)) |
205 | return exynos_gem; |
206 | |
207 | if (!is_drm_iommu_supported(drm_dev: dev) && (flags & EXYNOS_BO_NONCONTIG)) { |
208 | /* |
209 | * when no IOMMU is available, all allocated buffers are |
210 | * contiguous anyway, so drop EXYNOS_BO_NONCONTIG flag |
211 | */ |
212 | flags &= ~EXYNOS_BO_NONCONTIG; |
213 | DRM_WARN("Non-contiguous allocation is not supported without IOMMU, falling back to contiguous buffer\n" ); |
214 | } |
215 | |
216 | /* set memory type and cache attribute from user side. */ |
217 | exynos_gem->flags = flags; |
218 | |
219 | ret = exynos_drm_alloc_buf(exynos_gem, kvmap); |
220 | if (ret < 0) { |
221 | drm_gem_object_release(obj: &exynos_gem->base); |
222 | kfree(objp: exynos_gem); |
223 | return ERR_PTR(error: ret); |
224 | } |
225 | |
226 | return exynos_gem; |
227 | } |
228 | |
229 | int exynos_drm_gem_create_ioctl(struct drm_device *dev, void *data, |
230 | struct drm_file *file_priv) |
231 | { |
232 | struct drm_exynos_gem_create *args = data; |
233 | struct exynos_drm_gem *exynos_gem; |
234 | int ret; |
235 | |
236 | exynos_gem = exynos_drm_gem_create(dev, flags: args->flags, size: args->size, kvmap: false); |
237 | if (IS_ERR(ptr: exynos_gem)) |
238 | return PTR_ERR(ptr: exynos_gem); |
239 | |
240 | ret = exynos_drm_gem_handle_create(obj: &exynos_gem->base, file_priv, |
241 | handle: &args->handle); |
242 | if (ret) { |
243 | exynos_drm_gem_destroy(exynos_gem); |
244 | return ret; |
245 | } |
246 | |
247 | return 0; |
248 | } |
249 | |
250 | int exynos_drm_gem_map_ioctl(struct drm_device *dev, void *data, |
251 | struct drm_file *file_priv) |
252 | { |
253 | struct drm_exynos_gem_map *args = data; |
254 | |
255 | return drm_gem_dumb_map_offset(file: file_priv, dev, handle: args->handle, |
256 | offset: &args->offset); |
257 | } |
258 | |
259 | struct exynos_drm_gem *exynos_drm_gem_get(struct drm_file *filp, |
260 | unsigned int gem_handle) |
261 | { |
262 | struct drm_gem_object *obj; |
263 | |
264 | obj = drm_gem_object_lookup(filp, handle: gem_handle); |
265 | if (!obj) |
266 | return NULL; |
267 | return to_exynos_gem(obj); |
268 | } |
269 | |
270 | static int exynos_drm_gem_mmap_buffer(struct exynos_drm_gem *exynos_gem, |
271 | struct vm_area_struct *vma) |
272 | { |
273 | struct drm_device *drm_dev = exynos_gem->base.dev; |
274 | unsigned long vm_size; |
275 | int ret; |
276 | |
277 | vm_flags_clear(vma, VM_PFNMAP); |
278 | vma->vm_pgoff = 0; |
279 | |
280 | vm_size = vma->vm_end - vma->vm_start; |
281 | |
282 | /* check if user-requested size is valid. */ |
283 | if (vm_size > exynos_gem->size) |
284 | return -EINVAL; |
285 | |
286 | ret = dma_mmap_attrs(dev: to_dma_dev(dev: drm_dev), vma, cpu_addr: exynos_gem->cookie, |
287 | dma_addr: exynos_gem->dma_addr, size: exynos_gem->size, |
288 | attrs: exynos_gem->dma_attrs); |
289 | if (ret < 0) { |
290 | DRM_ERROR("failed to mmap.\n" ); |
291 | return ret; |
292 | } |
293 | |
294 | return 0; |
295 | } |
296 | |
297 | int exynos_drm_gem_get_ioctl(struct drm_device *dev, void *data, |
298 | struct drm_file *file_priv) |
299 | { |
300 | struct exynos_drm_gem *exynos_gem; |
301 | struct drm_exynos_gem_info *args = data; |
302 | struct drm_gem_object *obj; |
303 | |
304 | obj = drm_gem_object_lookup(filp: file_priv, handle: args->handle); |
305 | if (!obj) { |
306 | DRM_DEV_ERROR(dev->dev, "failed to lookup gem object.\n" ); |
307 | return -EINVAL; |
308 | } |
309 | |
310 | exynos_gem = to_exynos_gem(obj); |
311 | |
312 | args->flags = exynos_gem->flags; |
313 | args->size = exynos_gem->size; |
314 | |
315 | drm_gem_object_put(obj); |
316 | |
317 | return 0; |
318 | } |
319 | |
320 | void exynos_drm_gem_free_object(struct drm_gem_object *obj) |
321 | { |
322 | exynos_drm_gem_destroy(to_exynos_gem(obj)); |
323 | } |
324 | |
325 | int exynos_drm_gem_dumb_create(struct drm_file *file_priv, |
326 | struct drm_device *dev, |
327 | struct drm_mode_create_dumb *args) |
328 | { |
329 | struct exynos_drm_gem *exynos_gem; |
330 | unsigned int flags; |
331 | int ret; |
332 | |
333 | /* |
334 | * allocate memory to be used for framebuffer. |
335 | * - this callback would be called by user application |
336 | * with DRM_IOCTL_MODE_CREATE_DUMB command. |
337 | */ |
338 | |
339 | args->pitch = args->width * ((args->bpp + 7) / 8); |
340 | args->size = args->pitch * args->height; |
341 | |
342 | if (is_drm_iommu_supported(drm_dev: dev)) |
343 | flags = EXYNOS_BO_NONCONTIG | EXYNOS_BO_WC; |
344 | else |
345 | flags = EXYNOS_BO_CONTIG | EXYNOS_BO_WC; |
346 | |
347 | exynos_gem = exynos_drm_gem_create(dev, flags, size: args->size, kvmap: false); |
348 | if (IS_ERR(ptr: exynos_gem)) { |
349 | dev_warn(dev->dev, "FB allocation failed.\n" ); |
350 | return PTR_ERR(ptr: exynos_gem); |
351 | } |
352 | |
353 | ret = exynos_drm_gem_handle_create(obj: &exynos_gem->base, file_priv, |
354 | handle: &args->handle); |
355 | if (ret) { |
356 | exynos_drm_gem_destroy(exynos_gem); |
357 | return ret; |
358 | } |
359 | |
360 | return 0; |
361 | } |
362 | |
363 | static int exynos_drm_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma) |
364 | { |
365 | struct exynos_drm_gem *exynos_gem = to_exynos_gem(obj); |
366 | int ret; |
367 | |
368 | if (obj->import_attach) |
369 | return dma_buf_mmap(obj->dma_buf, vma, 0); |
370 | |
371 | vm_flags_set(vma, VM_IO | VM_DONTEXPAND | VM_DONTDUMP); |
372 | |
373 | DRM_DEV_DEBUG_KMS(to_dma_dev(obj->dev), "flags = 0x%x\n" , |
374 | exynos_gem->flags); |
375 | |
376 | /* non-cachable as default. */ |
377 | if (exynos_gem->flags & EXYNOS_BO_CACHABLE) |
378 | vma->vm_page_prot = vm_get_page_prot(vm_flags: vma->vm_flags); |
379 | else if (exynos_gem->flags & EXYNOS_BO_WC) |
380 | vma->vm_page_prot = |
381 | pgprot_writecombine(prot: vm_get_page_prot(vm_flags: vma->vm_flags)); |
382 | else |
383 | vma->vm_page_prot = |
384 | pgprot_noncached(vm_get_page_prot(vma->vm_flags)); |
385 | |
386 | ret = exynos_drm_gem_mmap_buffer(exynos_gem, vma); |
387 | if (ret) |
388 | goto err_close_vm; |
389 | |
390 | return ret; |
391 | |
392 | err_close_vm: |
393 | drm_gem_vm_close(vma); |
394 | |
395 | return ret; |
396 | } |
397 | |
398 | /* low-level interface prime helpers */ |
399 | struct drm_gem_object *exynos_drm_gem_prime_import(struct drm_device *dev, |
400 | struct dma_buf *dma_buf) |
401 | { |
402 | return drm_gem_prime_import_dev(dev, dma_buf, attach_dev: to_dma_dev(dev)); |
403 | } |
404 | |
405 | struct sg_table *exynos_drm_gem_prime_get_sg_table(struct drm_gem_object *obj) |
406 | { |
407 | struct exynos_drm_gem *exynos_gem = to_exynos_gem(obj); |
408 | struct drm_device *drm_dev = obj->dev; |
409 | struct sg_table *sgt; |
410 | int ret; |
411 | |
412 | sgt = kzalloc(size: sizeof(*sgt), GFP_KERNEL); |
413 | if (!sgt) |
414 | return ERR_PTR(error: -ENOMEM); |
415 | |
416 | ret = dma_get_sgtable_attrs(dev: to_dma_dev(dev: drm_dev), sgt, cpu_addr: exynos_gem->cookie, |
417 | dma_addr: exynos_gem->dma_addr, size: exynos_gem->size, |
418 | attrs: exynos_gem->dma_attrs); |
419 | if (ret) { |
420 | DRM_ERROR("failed to get sgtable, %d\n" , ret); |
421 | kfree(objp: sgt); |
422 | return ERR_PTR(error: ret); |
423 | } |
424 | |
425 | return sgt; |
426 | } |
427 | |
428 | struct drm_gem_object * |
429 | exynos_drm_gem_prime_import_sg_table(struct drm_device *dev, |
430 | struct dma_buf_attachment *attach, |
431 | struct sg_table *sgt) |
432 | { |
433 | struct exynos_drm_gem *exynos_gem; |
434 | |
435 | /* check if the entries in the sg_table are contiguous */ |
436 | if (drm_prime_get_contiguous_size(sgt) < attach->dmabuf->size) { |
437 | DRM_ERROR("buffer chunks must be mapped contiguously" ); |
438 | return ERR_PTR(error: -EINVAL); |
439 | } |
440 | |
441 | exynos_gem = exynos_drm_gem_init(dev, size: attach->dmabuf->size); |
442 | if (IS_ERR(ptr: exynos_gem)) |
443 | return ERR_CAST(ptr: exynos_gem); |
444 | |
445 | /* |
446 | * Buffer has been mapped as contiguous into DMA address space, |
447 | * but if there is IOMMU, it can be either CONTIG or NONCONTIG. |
448 | * We assume a simplified logic below: |
449 | */ |
450 | if (is_drm_iommu_supported(drm_dev: dev)) |
451 | exynos_gem->flags |= EXYNOS_BO_NONCONTIG; |
452 | else |
453 | exynos_gem->flags |= EXYNOS_BO_CONTIG; |
454 | |
455 | exynos_gem->dma_addr = sg_dma_address(sgt->sgl); |
456 | exynos_gem->sgt = sgt; |
457 | return &exynos_gem->base; |
458 | } |
459 | |