1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2011 The FreeBSD Foundation
5 *
6 * This software was developed by Konstantin Belousov under sponsorship from
7 * the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/cdefs.h>
32#include "opt_vm.h"
33
34#include <sys/param.h>
35#include <sys/systm.h>
36#include <sys/limits.h>
37#include <sys/lock.h>
38#include <sys/mutex.h>
39
40#include <vm/vm.h>
41#include <vm/vm_page.h>
42
43#include <dev/drm2/drmP.h>
44#include <dev/drm2/drm.h>
45#include <dev/drm2/drm_sarea.h>
46
47/*
48 * We make up offsets for buffer objects so we can recognize them at
49 * mmap time.
50 */
51
52/* pgoff in mmap is an unsigned long, so we need to make sure that
53 * the faked up offset will fit
54 */
55
56#if BITS_PER_LONG == 64
57#define DRM_FILE_PAGE_OFFSET_START ((0xFFFFFFFFUL >> PAGE_SHIFT) + 1)
58#define DRM_FILE_PAGE_OFFSET_SIZE ((0xFFFFFFFFUL >> PAGE_SHIFT) * 16)
59#else
60#define DRM_FILE_PAGE_OFFSET_START ((0xFFFFFFFUL >> PAGE_SHIFT) + 1)
61#define DRM_FILE_PAGE_OFFSET_SIZE ((0xFFFFFFFUL >> PAGE_SHIFT) * 16)
62#endif
63
64/**
65 * Initialize the GEM device fields
66 */
67
68int
69drm_gem_init(struct drm_device *dev)
70{
71	struct drm_gem_mm *mm;
72
73	drm_gem_names_init(&dev->object_names);
74
75	mm = malloc(sizeof(*mm), DRM_MEM_DRIVER, M_NOWAIT);
76	if (!mm) {
77		DRM_ERROR("out of memory\n");
78		return -ENOMEM;
79	}
80
81	dev->mm_private = mm;
82
83	if (drm_ht_create(&mm->offset_hash, 19)) {
84		free(mm, DRM_MEM_DRIVER);
85		return -ENOMEM;
86	}
87
88	mm->idxunr = new_unrhdr(0, DRM_GEM_MAX_IDX, NULL);
89
90	return 0;
91}
92
93void
94drm_gem_destroy(struct drm_device *dev)
95{
96	struct drm_gem_mm *mm = dev->mm_private;
97
98	dev->mm_private = NULL;
99	drm_ht_remove(&mm->offset_hash);
100	delete_unrhdr(mm->idxunr);
101	free(mm, DRM_MEM_DRIVER);
102	drm_gem_names_fini(&dev->object_names);
103}
104
105int drm_gem_object_init(struct drm_device *dev,
106			struct drm_gem_object *obj, size_t size)
107{
108	KASSERT((size & (PAGE_SIZE - 1)) == 0,
109	    ("Bad size %ju", (uintmax_t)size));
110
111	obj->dev = dev;
112	obj->vm_obj = vm_pager_allocate(OBJT_SWAP, NULL, size,
113	    VM_PROT_READ | VM_PROT_WRITE, 0, curthread->td_ucred);
114
115	obj->refcount = 1;
116	obj->handle_count = 0;
117	obj->size = size;
118
119	return 0;
120}
121EXPORT_SYMBOL(drm_gem_object_init);
122
123/**
124 * Initialize an already allocated GEM object of the specified size with
125 * no GEM provided backing store. Instead the caller is responsible for
126 * backing the object and handling it.
127 */
128int drm_gem_private_object_init(struct drm_device *dev,
129			struct drm_gem_object *obj, size_t size)
130{
131	MPASS((size & (PAGE_SIZE - 1)) == 0);
132
133	obj->dev = dev;
134	obj->vm_obj = NULL;
135
136	obj->refcount = 1;
137	atomic_store_rel_int(&obj->handle_count, 0);
138	obj->size = size;
139
140	return 0;
141}
142EXPORT_SYMBOL(drm_gem_private_object_init);
143
144struct drm_gem_object *
145drm_gem_object_alloc(struct drm_device *dev, size_t size)
146{
147	struct drm_gem_object *obj;
148
149	obj = malloc(sizeof(*obj), DRM_MEM_DRIVER, M_NOWAIT | M_ZERO);
150	if (!obj)
151		goto free;
152
153	if (drm_gem_object_init(dev, obj, size) != 0)
154		goto free;
155
156	if (dev->driver->gem_init_object != NULL &&
157	    dev->driver->gem_init_object(obj) != 0) {
158		goto dealloc;
159	}
160	return obj;
161dealloc:
162	vm_object_deallocate(obj->vm_obj);
163free:
164	free(obj, DRM_MEM_DRIVER);
165	return NULL;
166}
167EXPORT_SYMBOL(drm_gem_object_alloc);
168
169#if defined(FREEBSD_NOTYET)
170static void
171drm_gem_remove_prime_handles(struct drm_gem_object *obj, struct drm_file *filp)
172{
173	if (obj->import_attach) {
174		drm_prime_remove_buf_handle(&filp->prime,
175				obj->import_attach->dmabuf);
176	}
177	if (obj->export_dma_buf) {
178		drm_prime_remove_buf_handle(&filp->prime,
179				obj->export_dma_buf);
180	}
181}
182#endif
183
184/**
185 * Removes the mapping from handle to filp for this object.
186 */
187int
188drm_gem_handle_delete(struct drm_file *filp, u32 handle)
189{
190	struct drm_device *dev;
191	struct drm_gem_object *obj;
192
193	obj = drm_gem_names_remove(&filp->object_names, handle);
194	if (obj == NULL) {
195		return -EINVAL;
196	}
197	dev = obj->dev;
198
199#if defined(FREEBSD_NOTYET)
200	drm_gem_remove_prime_handles(obj, filp);
201#endif
202
203	if (dev->driver->gem_close_object)
204		dev->driver->gem_close_object(obj, filp);
205	drm_gem_object_handle_unreference_unlocked(obj);
206
207	return 0;
208}
209EXPORT_SYMBOL(drm_gem_handle_delete);
210
211/**
212 * Create a handle for this object. This adds a handle reference
213 * to the object, which includes a regular reference count. Callers
214 * will likely want to dereference the object afterwards.
215 */
216int
217drm_gem_handle_create(struct drm_file *file_priv,
218		       struct drm_gem_object *obj,
219		       u32 *handlep)
220{
221	struct drm_device *dev = obj->dev;
222	int ret;
223
224	*handlep = 0;
225	ret = drm_gem_name_create(&file_priv->object_names, obj, handlep);
226	if (ret != 0)
227		return ret;
228
229	drm_gem_object_handle_reference(obj);
230
231	if (dev->driver->gem_open_object) {
232		ret = dev->driver->gem_open_object(obj, file_priv);
233		if (ret) {
234			drm_gem_handle_delete(file_priv, *handlep);
235			return ret;
236		}
237	}
238
239	return 0;
240}
241EXPORT_SYMBOL(drm_gem_handle_create);
242
243void
244drm_gem_free_mmap_offset(struct drm_gem_object *obj)
245{
246	struct drm_device *dev = obj->dev;
247	struct drm_gem_mm *mm = dev->mm_private;
248	struct drm_hash_item *list = &obj->map_list;
249
250	if (!obj->on_map)
251		return;
252
253	drm_ht_remove_item(&mm->offset_hash, list);
254	free_unr(mm->idxunr, list->key);
255	obj->on_map = false;
256}
257EXPORT_SYMBOL(drm_gem_free_mmap_offset);
258
259int
260drm_gem_create_mmap_offset(struct drm_gem_object *obj)
261{
262	struct drm_device *dev = obj->dev;
263	struct drm_gem_mm *mm = dev->mm_private;
264	int ret;
265
266	if (obj->on_map)
267		return 0;
268
269	obj->map_list.key = alloc_unr(mm->idxunr);
270	ret = drm_ht_insert_item(&mm->offset_hash, &obj->map_list);
271	if (ret) {
272		DRM_ERROR("failed to add to map hash\n");
273		free_unr(mm->idxunr, obj->map_list.key);
274		return ret;
275	}
276	obj->on_map = true;
277
278	return 0;
279}
280EXPORT_SYMBOL(drm_gem_create_mmap_offset);
281
282/** Returns a reference to the object named by the handle. */
283struct drm_gem_object *
284drm_gem_object_lookup(struct drm_device *dev, struct drm_file *filp,
285		      u32 handle)
286{
287	struct drm_gem_object *obj;
288
289	obj = drm_gem_name_ref(&filp->object_names, handle,
290	    (void (*)(void *))drm_gem_object_reference);
291
292	return obj;
293}
294EXPORT_SYMBOL(drm_gem_object_lookup);
295
296int
297drm_gem_close_ioctl(struct drm_device *dev, void *data,
298		    struct drm_file *file_priv)
299{
300	struct drm_gem_close *args = data;
301	int ret;
302
303	if (!(dev->driver->driver_features & DRIVER_GEM))
304		return -ENODEV;
305
306	ret = drm_gem_handle_delete(file_priv, args->handle);
307
308	return ret;
309}
310
311int
312drm_gem_flink_ioctl(struct drm_device *dev, void *data,
313		    struct drm_file *file_priv)
314{
315	struct drm_gem_flink *args = data;
316	struct drm_gem_object *obj;
317	int ret;
318
319	if (!(dev->driver->driver_features & DRIVER_GEM))
320		return -ENODEV;
321
322	obj = drm_gem_object_lookup(dev, file_priv, args->handle);
323	if (obj == NULL)
324		return -ENOENT;
325
326	ret = drm_gem_name_create(&dev->object_names, obj, &obj->name);
327	if (ret != 0) {
328		if (ret == -EALREADY)
329			ret = 0;
330		drm_gem_object_unreference_unlocked(obj);
331	}
332	if (ret == 0)
333		args->name = obj->name;
334	return ret;
335}
336
337int
338drm_gem_open_ioctl(struct drm_device *dev, void *data,
339		   struct drm_file *file_priv)
340{
341	struct drm_gem_open *args = data;
342	struct drm_gem_object *obj;
343	int ret;
344	u32 handle;
345
346	if (!(dev->driver->driver_features & DRIVER_GEM))
347		return -ENODEV;
348
349	obj = drm_gem_name_ref(&dev->object_names, args->name,
350	    (void (*)(void *))drm_gem_object_reference);
351	if (!obj)
352		return -ENOENT;
353
354	ret = drm_gem_handle_create(file_priv, obj, &handle);
355	drm_gem_object_unreference_unlocked(obj);
356	if (ret)
357		return ret;
358
359	args->handle = handle;
360	args->size = obj->size;
361
362	return 0;
363}
364
365void
366drm_gem_open(struct drm_device *dev, struct drm_file *file_private)
367{
368
369	drm_gem_names_init(&file_private->object_names);
370}
371
372static int
373drm_gem_object_release_handle(uint32_t name, void *ptr, void *data)
374{
375	struct drm_file *file_priv = data;
376	struct drm_gem_object *obj = ptr;
377	struct drm_device *dev = obj->dev;
378
379#if defined(FREEBSD_NOTYET)
380	drm_gem_remove_prime_handles(obj, file_priv);
381#endif
382
383	if (dev->driver->gem_close_object)
384		dev->driver->gem_close_object(obj, file_priv);
385
386	drm_gem_object_handle_unreference_unlocked(obj);
387
388	return 0;
389}
390
391void
392drm_gem_release(struct drm_device *dev, struct drm_file *file_private)
393{
394	drm_gem_names_foreach(&file_private->object_names,
395	    drm_gem_object_release_handle, file_private);
396
397	drm_gem_names_fini(&file_private->object_names);
398}
399
400void
401drm_gem_object_release(struct drm_gem_object *obj)
402{
403
404	/*
405	 * obj->vm_obj can be NULL for private gem objects.
406	 */
407	vm_object_deallocate(obj->vm_obj);
408}
409EXPORT_SYMBOL(drm_gem_object_release);
410
411void
412drm_gem_object_free(struct drm_gem_object *obj)
413{
414	struct drm_device *dev = obj->dev;
415
416	DRM_LOCK_ASSERT(dev);
417	if (dev->driver->gem_free_object != NULL)
418		dev->driver->gem_free_object(obj);
419}
420EXPORT_SYMBOL(drm_gem_object_free);
421
422void drm_gem_object_handle_free(struct drm_gem_object *obj)
423{
424	struct drm_device *dev = obj->dev;
425	struct drm_gem_object *obj1;
426
427	if (obj->name) {
428		obj1 = drm_gem_names_remove(&dev->object_names, obj->name);
429		obj->name = 0;
430		drm_gem_object_unreference(obj1);
431	}
432}
433
434static struct drm_gem_object *
435drm_gem_object_from_offset(struct drm_device *dev, vm_ooffset_t offset)
436{
437	struct drm_gem_object *obj;
438	struct drm_gem_mm *mm;
439	struct drm_hash_item *map_list;
440
441	if ((offset & DRM_GEM_MAPPING_MASK) != DRM_GEM_MAPPING_KEY)
442		return (NULL);
443	offset &= ~DRM_GEM_MAPPING_KEY;
444	mm = dev->mm_private;
445	if (drm_ht_find_item(&mm->offset_hash, DRM_GEM_MAPPING_IDX(offset),
446	    &map_list) != 0) {
447	DRM_DEBUG("drm_gem_object_from_offset: offset 0x%jx obj not found\n",
448		    (uintmax_t)offset);
449		return (NULL);
450	}
451	obj = __containerof(map_list, struct drm_gem_object, map_list);
452	return (obj);
453}
454
455int
456drm_gem_mmap_single(struct drm_device *dev, vm_ooffset_t *offset, vm_size_t size,
457    struct vm_object **obj_res, int nprot)
458{
459	struct drm_gem_object *gem_obj;
460	struct vm_object *vm_obj;
461
462	DRM_LOCK(dev);
463	gem_obj = drm_gem_object_from_offset(dev, *offset);
464	if (gem_obj == NULL) {
465		DRM_UNLOCK(dev);
466		return (-ENODEV);
467	}
468	drm_gem_object_reference(gem_obj);
469	DRM_UNLOCK(dev);
470	vm_obj = cdev_pager_allocate(gem_obj, OBJT_MGTDEVICE,
471	    dev->driver->gem_pager_ops, size, nprot,
472	    DRM_GEM_MAPPING_MAPOFF(*offset), curthread->td_ucred);
473	if (vm_obj == NULL) {
474		drm_gem_object_unreference_unlocked(gem_obj);
475		return (-EINVAL);
476	}
477	*offset = DRM_GEM_MAPPING_MAPOFF(*offset);
478	*obj_res = vm_obj;
479	return (0);
480}
481
482void
483drm_gem_pager_dtr(void *handle)
484{
485	struct drm_gem_object *obj;
486	struct drm_device *dev;
487
488	obj = handle;
489	dev = obj->dev;
490
491	DRM_LOCK(dev);
492	drm_gem_free_mmap_offset(obj);
493	drm_gem_object_unreference(obj);
494	DRM_UNLOCK(dev);
495}
496