1/**************************************************************************
2 *
3 * Copyright (c) 2009 VMware, Inc., Palo Alto, CA., USA
4 * All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sub license, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice (including the
15 * next paragraph) shall be included in all copies or substantial portions
16 * of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
21 * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
22 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24 * USE OR OTHER DEALINGS IN THE SOFTWARE.
25 *
26 **************************************************************************/
27/*
28 * Authors: Thomas Hellstrom <thellstrom-at-vmware-dot-com>
29 */
30/** @file ttm_ref_object.c
31 *
32 * Base- and reference object implementation for the various
33 * ttm objects. Implements reference counting, minimal security checks
34 * and release on file close.
35 */
36
37
38#include <sys/cdefs.h>
39__FBSDID("$FreeBSD$");
40
41/**
42 * struct ttm_object_file
43 *
44 * @tdev: Pointer to the ttm_object_device.
45 *
46 * @lock: Lock that protects the ref_list list and the
47 * ref_hash hash tables.
48 *
49 * @ref_list: List of ttm_ref_objects to be destroyed at
50 * file release.
51 *
52 * @ref_hash: Hash tables of ref objects, one per ttm_ref_type,
53 * for fast lookup of ref objects given a base object.
54 */
55
56#define pr_fmt(fmt) "[TTM] " fmt
57
58#include <dev/drm2/drmP.h>
59#include <dev/drm2/drm.h>
60#include <sys/rwlock.h>
61#include <dev/drm2/ttm/ttm_object.h>
62#include <dev/drm2/ttm/ttm_module.h>
63
64struct ttm_object_file {
65	struct ttm_object_device *tdev;
66	struct rwlock lock;
67	struct list_head ref_list;
68	struct drm_open_hash ref_hash[TTM_REF_NUM];
69	u_int refcount;
70};
71
72/**
73 * struct ttm_object_device
74 *
75 * @object_lock: lock that protects the object_hash hash table.
76 *
77 * @object_hash: hash table for fast lookup of object global names.
78 *
79 * @object_count: Per device object count.
80 *
81 * This is the per-device data structure needed for ttm object management.
82 */
83
84struct ttm_object_device {
85	struct rwlock object_lock;
86	struct drm_open_hash object_hash;
87	atomic_t object_count;
88	struct ttm_mem_global *mem_glob;
89};
90
91/**
92 * struct ttm_ref_object
93 *
94 * @hash: Hash entry for the per-file object reference hash.
95 *
96 * @head: List entry for the per-file list of ref-objects.
97 *
98 * @kref: Ref count.
99 *
100 * @obj: Base object this ref object is referencing.
101 *
102 * @ref_type: Type of ref object.
103 *
104 * This is similar to an idr object, but it also has a hash table entry
105 * that allows lookup with a pointer to the referenced object as a key. In
106 * that way, one can easily detect whether a base object is referenced by
107 * a particular ttm_object_file. It also carries a ref count to avoid creating
108 * multiple ref objects if a ttm_object_file references the same base
109 * object more than once.
110 */
111
112struct ttm_ref_object {
113	struct drm_hash_item hash;
114	struct list_head head;
115	u_int kref;
116	enum ttm_ref_type ref_type;
117	struct ttm_base_object *obj;
118	struct ttm_object_file *tfile;
119};
120
121MALLOC_DEFINE(M_TTM_OBJ_FILE, "ttm_obj_file", "TTM File Objects");
122
123static inline struct ttm_object_file *
124ttm_object_file_ref(struct ttm_object_file *tfile)
125{
126	refcount_acquire(&tfile->refcount);
127	return tfile;
128}
129
130static void ttm_object_file_destroy(struct ttm_object_file *tfile)
131{
132
133	free(tfile, M_TTM_OBJ_FILE);
134}
135
136
137static inline void ttm_object_file_unref(struct ttm_object_file **p_tfile)
138{
139	struct ttm_object_file *tfile = *p_tfile;
140
141	*p_tfile = NULL;
142	if (refcount_release(&tfile->refcount))
143		ttm_object_file_destroy(tfile);
144}
145
146
147int ttm_base_object_init(struct ttm_object_file *tfile,
148			 struct ttm_base_object *base,
149			 bool shareable,
150			 enum ttm_object_type object_type,
151			 void (*rcount_release) (struct ttm_base_object **),
152			 void (*ref_obj_release) (struct ttm_base_object *,
153						  enum ttm_ref_type ref_type))
154{
155	struct ttm_object_device *tdev = tfile->tdev;
156	int ret;
157
158	base->shareable = shareable;
159	base->tfile = ttm_object_file_ref(tfile);
160	base->refcount_release = rcount_release;
161	base->ref_obj_release = ref_obj_release;
162	base->object_type = object_type;
163	refcount_init(&base->refcount, 1);
164	rw_init(&tdev->object_lock, "ttmbao");
165	rw_wlock(&tdev->object_lock);
166	ret = drm_ht_just_insert_please(&tdev->object_hash,
167					    &base->hash,
168					    (unsigned long)base, 31, 0, 0);
169	rw_wunlock(&tdev->object_lock);
170	if (unlikely(ret != 0))
171		goto out_err0;
172
173	ret = ttm_ref_object_add(tfile, base, TTM_REF_USAGE, NULL);
174	if (unlikely(ret != 0))
175		goto out_err1;
176
177	ttm_base_object_unref(&base);
178
179	return 0;
180out_err1:
181	rw_wlock(&tdev->object_lock);
182	(void)drm_ht_remove_item(&tdev->object_hash, &base->hash);
183	rw_wunlock(&tdev->object_lock);
184out_err0:
185	return ret;
186}
187
188static void ttm_release_base(struct ttm_base_object *base)
189{
190	struct ttm_object_device *tdev = base->tfile->tdev;
191
192	(void)drm_ht_remove_item(&tdev->object_hash, &base->hash);
193	rw_wunlock(&tdev->object_lock);
194	/*
195	 * Note: We don't use synchronize_rcu() here because it's far
196	 * too slow. It's up to the user to free the object using
197	 * call_rcu() or ttm_base_object_kfree().
198	 */
199
200	if (base->refcount_release) {
201		ttm_object_file_unref(&base->tfile);
202		base->refcount_release(&base);
203	}
204	rw_wlock(&tdev->object_lock);
205}
206
207void ttm_base_object_unref(struct ttm_base_object **p_base)
208{
209	struct ttm_base_object *base = *p_base;
210	struct ttm_object_device *tdev = base->tfile->tdev;
211
212	*p_base = NULL;
213
214	/*
215	 * Need to take the lock here to avoid racing with
216	 * users trying to look up the object.
217	 */
218
219	rw_wlock(&tdev->object_lock);
220	if (refcount_release(&base->refcount))
221		ttm_release_base(base);
222	rw_wunlock(&tdev->object_lock);
223}
224
225struct ttm_base_object *ttm_base_object_lookup(struct ttm_object_file *tfile,
226					       uint32_t key)
227{
228	struct ttm_object_device *tdev = tfile->tdev;
229	struct ttm_base_object *base;
230	struct drm_hash_item *hash;
231	int ret;
232
233	rw_rlock(&tdev->object_lock);
234	ret = drm_ht_find_item(&tdev->object_hash, key, &hash);
235
236	if (ret == 0) {
237		base = drm_hash_entry(hash, struct ttm_base_object, hash);
238		refcount_acquire(&base->refcount);
239	}
240	rw_runlock(&tdev->object_lock);
241
242	if (unlikely(ret != 0))
243		return NULL;
244
245	if (tfile != base->tfile && !base->shareable) {
246		printf("[TTM] Attempted access of non-shareable object %p\n",
247		    base);
248		ttm_base_object_unref(&base);
249		return NULL;
250	}
251
252	return base;
253}
254
255MALLOC_DEFINE(M_TTM_OBJ_REF, "ttm_obj_ref", "TTM Ref Objects");
256
257int ttm_ref_object_add(struct ttm_object_file *tfile,
258		       struct ttm_base_object *base,
259		       enum ttm_ref_type ref_type, bool *existed)
260{
261	struct drm_open_hash *ht = &tfile->ref_hash[ref_type];
262	struct ttm_ref_object *ref;
263	struct drm_hash_item *hash;
264	struct ttm_mem_global *mem_glob = tfile->tdev->mem_glob;
265	int ret = -EINVAL;
266
267	if (existed != NULL)
268		*existed = true;
269
270	while (ret == -EINVAL) {
271		rw_rlock(&tfile->lock);
272		ret = drm_ht_find_item(ht, base->hash.key, &hash);
273
274		if (ret == 0) {
275			ref = drm_hash_entry(hash, struct ttm_ref_object, hash);
276			refcount_acquire(&ref->kref);
277			rw_runlock(&tfile->lock);
278			break;
279		}
280
281		rw_runlock(&tfile->lock);
282		ret = ttm_mem_global_alloc(mem_glob, sizeof(*ref),
283					   false, false);
284		if (unlikely(ret != 0))
285			return ret;
286		ref = malloc(sizeof(*ref), M_TTM_OBJ_REF, M_WAITOK);
287		if (unlikely(ref == NULL)) {
288			ttm_mem_global_free(mem_glob, sizeof(*ref));
289			return -ENOMEM;
290		}
291
292		ref->hash.key = base->hash.key;
293		ref->obj = base;
294		ref->tfile = tfile;
295		ref->ref_type = ref_type;
296		refcount_init(&ref->kref, 1);
297
298		rw_wlock(&tfile->lock);
299		ret = drm_ht_insert_item(ht, &ref->hash);
300
301		if (ret == 0) {
302			list_add_tail(&ref->head, &tfile->ref_list);
303			refcount_acquire(&base->refcount);
304			rw_wunlock(&tfile->lock);
305			if (existed != NULL)
306				*existed = false;
307			break;
308		}
309
310		rw_wunlock(&tfile->lock);
311		MPASS(ret == -EINVAL);
312
313		ttm_mem_global_free(mem_glob, sizeof(*ref));
314		free(ref, M_TTM_OBJ_REF);
315	}
316
317	return ret;
318}
319
320static void ttm_ref_object_release(struct ttm_ref_object *ref)
321{
322	struct ttm_base_object *base = ref->obj;
323	struct ttm_object_file *tfile = ref->tfile;
324	struct drm_open_hash *ht;
325	struct ttm_mem_global *mem_glob = tfile->tdev->mem_glob;
326
327	ht = &tfile->ref_hash[ref->ref_type];
328	(void)drm_ht_remove_item(ht, &ref->hash);
329	list_del(&ref->head);
330	rw_wunlock(&tfile->lock);
331
332	if (ref->ref_type != TTM_REF_USAGE && base->ref_obj_release)
333		base->ref_obj_release(base, ref->ref_type);
334
335	ttm_base_object_unref(&ref->obj);
336	ttm_mem_global_free(mem_glob, sizeof(*ref));
337	free(ref, M_TTM_OBJ_REF);
338	rw_wlock(&tfile->lock);
339}
340
341int ttm_ref_object_base_unref(struct ttm_object_file *tfile,
342			      unsigned long key, enum ttm_ref_type ref_type)
343{
344	struct drm_open_hash *ht = &tfile->ref_hash[ref_type];
345	struct ttm_ref_object *ref;
346	struct drm_hash_item *hash;
347	int ret;
348
349	rw_wlock(&tfile->lock);
350	ret = drm_ht_find_item(ht, key, &hash);
351	if (unlikely(ret != 0)) {
352		rw_wunlock(&tfile->lock);
353		return -EINVAL;
354	}
355	ref = drm_hash_entry(hash, struct ttm_ref_object, hash);
356	if (refcount_release(&ref->kref))
357		ttm_ref_object_release(ref);
358	rw_wunlock(&tfile->lock);
359	return 0;
360}
361
362void ttm_object_file_release(struct ttm_object_file **p_tfile)
363{
364	struct ttm_ref_object *ref;
365	struct list_head *list;
366	unsigned int i;
367	struct ttm_object_file *tfile = *p_tfile;
368
369	*p_tfile = NULL;
370	rw_wlock(&tfile->lock);
371
372	/*
373	 * Since we release the lock within the loop, we have to
374	 * restart it from the beginning each time.
375	 */
376
377	while (!list_empty(&tfile->ref_list)) {
378		list = tfile->ref_list.next;
379		ref = list_entry(list, struct ttm_ref_object, head);
380		ttm_ref_object_release(ref);
381	}
382
383	for (i = 0; i < TTM_REF_NUM; ++i)
384		drm_ht_remove(&tfile->ref_hash[i]);
385
386	rw_wunlock(&tfile->lock);
387	ttm_object_file_unref(&tfile);
388}
389
390struct ttm_object_file *ttm_object_file_init(struct ttm_object_device *tdev,
391					     unsigned int hash_order)
392{
393	struct ttm_object_file *tfile;
394	unsigned int i;
395	unsigned int j = 0;
396	int ret;
397
398	tfile = malloc(sizeof(*tfile), M_TTM_OBJ_FILE, M_WAITOK);
399	rw_init(&tfile->lock, "ttmfo");
400	tfile->tdev = tdev;
401	refcount_init(&tfile->refcount, 1);
402	INIT_LIST_HEAD(&tfile->ref_list);
403
404	for (i = 0; i < TTM_REF_NUM; ++i) {
405		ret = drm_ht_create(&tfile->ref_hash[i], hash_order);
406		if (ret) {
407			j = i;
408			goto out_err;
409		}
410	}
411
412	return tfile;
413out_err:
414	for (i = 0; i < j; ++i)
415		drm_ht_remove(&tfile->ref_hash[i]);
416
417	free(tfile, M_TTM_OBJ_FILE);
418
419	return NULL;
420}
421
422MALLOC_DEFINE(M_TTM_OBJ_DEV, "ttm_obj_dev", "TTM Device Objects");
423
424struct ttm_object_device *ttm_object_device_init(struct ttm_mem_global
425						 *mem_glob,
426						 unsigned int hash_order)
427{
428	struct ttm_object_device *tdev;
429	int ret;
430
431	tdev = malloc(sizeof(*tdev), M_TTM_OBJ_DEV, M_WAITOK);
432	tdev->mem_glob = mem_glob;
433	rw_init(&tdev->object_lock, "ttmdo");
434	atomic_set(&tdev->object_count, 0);
435	ret = drm_ht_create(&tdev->object_hash, hash_order);
436
437	if (ret == 0)
438		return tdev;
439
440	free(tdev, M_TTM_OBJ_DEV);
441	return NULL;
442}
443
444void ttm_object_device_release(struct ttm_object_device **p_tdev)
445{
446	struct ttm_object_device *tdev = *p_tdev;
447
448	*p_tdev = NULL;
449
450	rw_wlock(&tdev->object_lock);
451	drm_ht_remove(&tdev->object_hash);
452	rw_wunlock(&tdev->object_lock);
453
454	free(tdev, M_TTM_OBJ_DEV);
455}
456