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