1 /* $NetBSD: drm_context.c,v 1.9 2021/12/19 12:30:04 riastradh Exp $ */
2
3 /*
4 * Legacy: Generic DRM Contexts
5 *
6 * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
7 * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
8 * All Rights Reserved.
9 *
10 * Author: Rickard E. (Rik) Faith <faith@valinux.com>
11 * Author: Gareth Hughes <gareth@valinux.com>
12 *
13 * Permission is hereby granted, free of charge, to any person obtaining a
14 * copy of this software and associated documentation files (the "Software"),
15 * to deal in the Software without restriction, including without limitation
16 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 * and/or sell copies of the Software, and to permit persons to whom the
18 * Software is furnished to do so, subject to the following conditions:
19 *
20 * The above copyright notice and this permission notice (including the next
21 * paragraph) shall be included in all copies or substantial portions of the
22 * Software.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
27 * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
28 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
29 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
30 * OTHER DEALINGS IN THE SOFTWARE.
31 */
32
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: drm_context.c,v 1.9 2021/12/19 12:30:04 riastradh Exp $");
35
36 #include <linux/err.h>
37 #include <linux/slab.h>
38 #include <linux/uaccess.h>
39
40 #include <drm/drm_drv.h>
41 #include <drm/drm_file.h>
42 #include <drm/drm_print.h>
43
44 #include "drm_legacy.h"
45
46 struct drm_ctx_list {
47 struct list_head head;
48 drm_context_t handle;
49 struct drm_file *tag;
50 };
51
52 /******************************************************************/
53 /** \name Context bitmap support */
54 /*@{*/
55
56 /**
57 * Free a handle from the context bitmap.
58 *
59 * \param dev DRM device.
60 * \param ctx_handle context handle.
61 *
62 * Clears the bit specified by \p ctx_handle in drm_device::ctx_bitmap and the entry
63 * in drm_device::ctx_idr, while holding the drm_device::struct_mutex
64 * lock.
65 */
drm_legacy_ctxbitmap_free(struct drm_device * dev,int ctx_handle)66 void drm_legacy_ctxbitmap_free(struct drm_device * dev, int ctx_handle)
67 {
68 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
69 !drm_core_check_feature(dev, DRIVER_LEGACY))
70 return;
71
72 mutex_lock(&dev->struct_mutex);
73 idr_remove(&dev->ctx_idr, ctx_handle);
74 mutex_unlock(&dev->struct_mutex);
75 }
76
77 /**
78 * Context bitmap allocation.
79 *
80 * \param dev DRM device.
81 * \return (non-negative) context handle on success or a negative number on failure.
82 *
83 * Allocate a new idr from drm_device::ctx_idr while holding the
84 * drm_device::struct_mutex lock.
85 */
drm_legacy_ctxbitmap_next(struct drm_device * dev)86 static int drm_legacy_ctxbitmap_next(struct drm_device * dev)
87 {
88 int ret;
89
90 idr_preload(GFP_KERNEL);
91 mutex_lock(&dev->struct_mutex);
92 ret = idr_alloc(&dev->ctx_idr, NULL, DRM_RESERVED_CONTEXTS, 0,
93 GFP_KERNEL);
94 mutex_unlock(&dev->struct_mutex);
95 idr_preload_end();
96 return ret;
97 }
98
99 /**
100 * Context bitmap initialization.
101 *
102 * \param dev DRM device.
103 *
104 * Initialise the drm_device::ctx_idr
105 */
drm_legacy_ctxbitmap_init(struct drm_device * dev)106 void drm_legacy_ctxbitmap_init(struct drm_device * dev)
107 {
108 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
109 !drm_core_check_feature(dev, DRIVER_LEGACY))
110 return;
111
112 idr_init(&dev->ctx_idr);
113 }
114
115 /**
116 * Context bitmap cleanup.
117 *
118 * \param dev DRM device.
119 *
120 * Free all idr members using drm_ctx_sarea_free helper function
121 * while holding the drm_device::struct_mutex lock.
122 */
drm_legacy_ctxbitmap_cleanup(struct drm_device * dev)123 void drm_legacy_ctxbitmap_cleanup(struct drm_device * dev)
124 {
125 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
126 !drm_core_check_feature(dev, DRIVER_LEGACY))
127 return;
128
129 mutex_lock(&dev->struct_mutex);
130 idr_destroy(&dev->ctx_idr);
131 mutex_unlock(&dev->struct_mutex);
132 }
133
134 /**
135 * drm_ctxbitmap_flush() - Flush all contexts owned by a file
136 * @dev: DRM device to operate on
137 * @file: Open file to flush contexts for
138 *
139 * This iterates over all contexts on @dev and drops them if they're owned by
140 * @file. Note that after this call returns, new contexts might be added if
141 * the file is still alive.
142 */
drm_legacy_ctxbitmap_flush(struct drm_device * dev,struct drm_file * file)143 void drm_legacy_ctxbitmap_flush(struct drm_device *dev, struct drm_file *file)
144 {
145 struct drm_ctx_list *pos, *tmp;
146
147 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
148 !drm_core_check_feature(dev, DRIVER_LEGACY))
149 return;
150
151 mutex_lock(&dev->ctxlist_mutex);
152
153 list_for_each_entry_safe(pos, tmp, &dev->ctxlist, head) {
154 if (pos->tag == file &&
155 pos->handle != DRM_KERNEL_CONTEXT) {
156 if (dev->driver->context_dtor)
157 dev->driver->context_dtor(dev, pos->handle);
158
159 drm_legacy_ctxbitmap_free(dev, pos->handle);
160 list_del(&pos->head);
161 kfree(pos);
162 }
163 }
164
165 mutex_unlock(&dev->ctxlist_mutex);
166 }
167
168 /*@}*/
169
170 /******************************************************************/
171 /** \name Per Context SAREA Support */
172 /*@{*/
173
174 /**
175 * Get per-context SAREA.
176 *
177 * \param inode device inode.
178 * \param file_priv DRM file private.
179 * \param cmd command.
180 * \param arg user argument pointing to a drm_ctx_priv_map structure.
181 * \return zero on success or a negative number on failure.
182 *
183 * Gets the map from drm_device::ctx_idr with the handle specified and
184 * returns its handle.
185 */
drm_legacy_getsareactx(struct drm_device * dev,void * data,struct drm_file * file_priv)186 int drm_legacy_getsareactx(struct drm_device *dev, void *data,
187 struct drm_file *file_priv)
188 {
189 struct drm_ctx_priv_map *request = data;
190 struct drm_local_map *map;
191 struct drm_map_list *_entry;
192
193 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
194 !drm_core_check_feature(dev, DRIVER_LEGACY))
195 return -EOPNOTSUPP;
196
197 mutex_lock(&dev->struct_mutex);
198
199 map = idr_find(&dev->ctx_idr, request->ctx_id);
200 if (!map) {
201 mutex_unlock(&dev->struct_mutex);
202 return -EINVAL;
203 }
204
205 request->handle = NULL;
206 list_for_each_entry(_entry, &dev->maplist, head) {
207 if (_entry->map == map) {
208 request->handle =
209 (void *)(unsigned long)_entry->user_token;
210 break;
211 }
212 }
213
214 mutex_unlock(&dev->struct_mutex);
215
216 if (request->handle == NULL)
217 return -EINVAL;
218
219 return 0;
220 }
221
222 /**
223 * Set per-context SAREA.
224 *
225 * \param inode device inode.
226 * \param file_priv DRM file private.
227 * \param cmd command.
228 * \param arg user argument pointing to a drm_ctx_priv_map structure.
229 * \return zero on success or a negative number on failure.
230 *
231 * Searches the mapping specified in \p arg and update the entry in
232 * drm_device::ctx_idr with it.
233 */
drm_legacy_setsareactx(struct drm_device * dev,void * data,struct drm_file * file_priv)234 int drm_legacy_setsareactx(struct drm_device *dev, void *data,
235 struct drm_file *file_priv)
236 {
237 struct drm_ctx_priv_map *request = data;
238 struct drm_local_map *map = NULL;
239 struct drm_map_list *r_list = NULL;
240
241 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
242 !drm_core_check_feature(dev, DRIVER_LEGACY))
243 return -EOPNOTSUPP;
244
245 mutex_lock(&dev->struct_mutex);
246 list_for_each_entry(r_list, &dev->maplist, head) {
247 if (r_list->map
248 && r_list->user_token == (unsigned long) request->handle)
249 goto found;
250 }
251 bad:
252 mutex_unlock(&dev->struct_mutex);
253 return -EINVAL;
254
255 found:
256 map = r_list->map;
257 if (!map)
258 goto bad;
259
260 if (IS_ERR(idr_replace(&dev->ctx_idr, map, request->ctx_id)))
261 goto bad;
262
263 mutex_unlock(&dev->struct_mutex);
264
265 return 0;
266 }
267
268 /*@}*/
269
270 /******************************************************************/
271 /** \name The actual DRM context handling routines */
272 /*@{*/
273
274 /**
275 * Switch context.
276 *
277 * \param dev DRM device.
278 * \param old old context handle.
279 * \param new new context handle.
280 * \return zero on success or a negative number on failure.
281 *
282 * Attempt to set drm_device::context_flag.
283 */
drm_context_switch(struct drm_device * dev,int old,int new)284 static int drm_context_switch(struct drm_device * dev, int old, int new)
285 {
286 if (test_and_set_bit(0, &dev->context_flag)) {
287 DRM_ERROR("Reentering -- FIXME\n");
288 return -EBUSY;
289 }
290
291 DRM_DEBUG("Context switch from %d to %d\n", old, new);
292
293 if (new == dev->last_context) {
294 clear_bit(0, &dev->context_flag);
295 return 0;
296 }
297
298 return 0;
299 }
300
301 /**
302 * Complete context switch.
303 *
304 * \param dev DRM device.
305 * \param new new context handle.
306 * \return zero on success or a negative number on failure.
307 *
308 * Updates drm_device::last_context and drm_device::last_switch. Verifies the
309 * hardware lock is held, clears the drm_device::context_flag and wakes up
310 * drm_device::context_wait.
311 */
drm_context_switch_complete(struct drm_device * dev,struct drm_file * file_priv,int new)312 static int drm_context_switch_complete(struct drm_device *dev,
313 struct drm_file *file_priv, int new)
314 {
315 dev->last_context = new; /* PRE/POST: This is the _only_ writer. */
316
317 spin_lock(&file_priv->master->lock.spinlock);
318 if (file_priv->master->lock.hw_lock == NULL ||
319 !_DRM_LOCK_IS_HELD(file_priv->master->lock.hw_lock->lock)) {
320 DRM_ERROR("Lock isn't held after context switch\n");
321 }
322 spin_unlock(&file_priv->master->lock.spinlock);
323
324 /* If a context switch is ever initiated
325 when the kernel holds the lock, release
326 that lock here. */
327 clear_bit(0, &dev->context_flag);
328
329 return 0;
330 }
331
332 /**
333 * Reserve contexts.
334 *
335 * \param inode device inode.
336 * \param file_priv DRM file private.
337 * \param cmd command.
338 * \param arg user argument pointing to a drm_ctx_res structure.
339 * \return zero on success or a negative number on failure.
340 */
drm_legacy_resctx(struct drm_device * dev,void * data,struct drm_file * file_priv)341 int drm_legacy_resctx(struct drm_device *dev, void *data,
342 struct drm_file *file_priv)
343 {
344 struct drm_ctx_res *res = data;
345 struct drm_ctx ctx;
346 int i;
347
348 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
349 !drm_core_check_feature(dev, DRIVER_LEGACY))
350 return -EOPNOTSUPP;
351
352 if (res->count >= DRM_RESERVED_CONTEXTS) {
353 memset(&ctx, 0, sizeof(ctx));
354 for (i = 0; i < DRM_RESERVED_CONTEXTS; i++) {
355 ctx.handle = i;
356 if (copy_to_user(&res->contexts[i], &ctx, sizeof(ctx)))
357 return -EFAULT;
358 }
359 }
360 res->count = DRM_RESERVED_CONTEXTS;
361
362 return 0;
363 }
364
365 /**
366 * Add context.
367 *
368 * \param inode device inode.
369 * \param file_priv DRM file private.
370 * \param cmd command.
371 * \param arg user argument pointing to a drm_ctx structure.
372 * \return zero on success or a negative number on failure.
373 *
374 * Get a new handle for the context and copy to userspace.
375 */
drm_legacy_addctx(struct drm_device * dev,void * data,struct drm_file * file_priv)376 int drm_legacy_addctx(struct drm_device *dev, void *data,
377 struct drm_file *file_priv)
378 {
379 struct drm_ctx_list *ctx_entry;
380 struct drm_ctx *ctx = data;
381 int tmp_handle;
382
383 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
384 !drm_core_check_feature(dev, DRIVER_LEGACY))
385 return -EOPNOTSUPP;
386
387 tmp_handle = drm_legacy_ctxbitmap_next(dev);
388 if (tmp_handle == DRM_KERNEL_CONTEXT) {
389 /* Skip kernel's context and get a new one. */
390 tmp_handle = drm_legacy_ctxbitmap_next(dev);
391 }
392 DRM_DEBUG("%d\n", tmp_handle);
393 if ((int)ctx->handle < 0) {
394 DRM_DEBUG("Not enough free contexts.\n");
395 /* Should this return -EBUSY instead? */
396 return tmp_handle;
397 }
398
399 ctx->handle = tmp_handle;
400
401 ctx_entry = kmalloc(sizeof(*ctx_entry), GFP_KERNEL);
402 if (!ctx_entry) {
403 DRM_DEBUG("out of memory\n");
404 return -ENOMEM;
405 }
406
407 INIT_LIST_HEAD(&ctx_entry->head);
408 ctx_entry->handle = ctx->handle;
409 ctx_entry->tag = file_priv;
410
411 mutex_lock(&dev->ctxlist_mutex);
412 list_add(&ctx_entry->head, &dev->ctxlist);
413 mutex_unlock(&dev->ctxlist_mutex);
414
415 return 0;
416 }
417
418 /**
419 * Get context.
420 *
421 * \param inode device inode.
422 * \param file_priv DRM file private.
423 * \param cmd command.
424 * \param arg user argument pointing to a drm_ctx structure.
425 * \return zero on success or a negative number on failure.
426 */
drm_legacy_getctx(struct drm_device * dev,void * data,struct drm_file * file_priv)427 int drm_legacy_getctx(struct drm_device *dev, void *data,
428 struct drm_file *file_priv)
429 {
430 struct drm_ctx *ctx = data;
431
432 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
433 !drm_core_check_feature(dev, DRIVER_LEGACY))
434 return -EOPNOTSUPP;
435
436 /* This is 0, because we don't handle any context flags */
437 ctx->flags = 0;
438
439 return 0;
440 }
441
442 /**
443 * Switch context.
444 *
445 * \param inode device inode.
446 * \param file_priv DRM file private.
447 * \param cmd command.
448 * \param arg user argument pointing to a drm_ctx structure.
449 * \return zero on success or a negative number on failure.
450 *
451 * Calls context_switch().
452 */
drm_legacy_switchctx(struct drm_device * dev,void * data,struct drm_file * file_priv)453 int drm_legacy_switchctx(struct drm_device *dev, void *data,
454 struct drm_file *file_priv)
455 {
456 struct drm_ctx *ctx = data;
457
458 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
459 !drm_core_check_feature(dev, DRIVER_LEGACY))
460 return -EOPNOTSUPP;
461
462 DRM_DEBUG("%d\n", ctx->handle);
463 return drm_context_switch(dev, dev->last_context, ctx->handle);
464 }
465
466 /**
467 * New context.
468 *
469 * \param inode device inode.
470 * \param file_priv DRM file private.
471 * \param cmd command.
472 * \param arg user argument pointing to a drm_ctx structure.
473 * \return zero on success or a negative number on failure.
474 *
475 * Calls context_switch_complete().
476 */
drm_legacy_newctx(struct drm_device * dev,void * data,struct drm_file * file_priv)477 int drm_legacy_newctx(struct drm_device *dev, void *data,
478 struct drm_file *file_priv)
479 {
480 struct drm_ctx *ctx = data;
481
482 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
483 !drm_core_check_feature(dev, DRIVER_LEGACY))
484 return -EOPNOTSUPP;
485
486 DRM_DEBUG("%d\n", ctx->handle);
487 drm_context_switch_complete(dev, file_priv, ctx->handle);
488
489 return 0;
490 }
491
492 /**
493 * Remove context.
494 *
495 * \param inode device inode.
496 * \param file_priv DRM file private.
497 * \param cmd command.
498 * \param arg user argument pointing to a drm_ctx structure.
499 * \return zero on success or a negative number on failure.
500 *
501 * If not the special kernel context, calls ctxbitmap_free() to free the specified context.
502 */
drm_legacy_rmctx(struct drm_device * dev,void * data,struct drm_file * file_priv)503 int drm_legacy_rmctx(struct drm_device *dev, void *data,
504 struct drm_file *file_priv)
505 {
506 struct drm_ctx *ctx = data;
507
508 if (!drm_core_check_feature(dev, DRIVER_KMS_LEGACY_CONTEXT) &&
509 !drm_core_check_feature(dev, DRIVER_LEGACY))
510 return -EOPNOTSUPP;
511
512 DRM_DEBUG("%d\n", ctx->handle);
513 if (ctx->handle != DRM_KERNEL_CONTEXT) {
514 if (dev->driver->context_dtor)
515 dev->driver->context_dtor(dev, ctx->handle);
516 drm_legacy_ctxbitmap_free(dev, ctx->handle);
517 }
518
519 mutex_lock(&dev->ctxlist_mutex);
520 if (!list_empty(&dev->ctxlist)) {
521 struct drm_ctx_list *pos, *n;
522
523 list_for_each_entry_safe(pos, n, &dev->ctxlist, head) {
524 if (pos->handle == ctx->handle) {
525 list_del(&pos->head);
526 kfree(pos);
527 }
528 }
529 }
530 mutex_unlock(&dev->ctxlist_mutex);
531
532 return 0;
533 }
534
535 /*@}*/
536