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