xref: /third_party/mesa3d/src/drm-shim/device.c (revision bf215546)
1/*
2 * Copyright © 2018 Broadcom
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
24/** @file
25 *
26 * Implements core GEM support (particularly ioctls) underneath the libc ioctl
27 * wrappers, and calls into the driver-specific code as necessary.
28 */
29
30#include <c11/threads.h>
31#include <errno.h>
32#include <linux/memfd.h>
33#include <stdbool.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <sys/ioctl.h>
38#include <sys/mman.h>
39#include <unistd.h>
40#include "drm-uapi/drm.h"
41#include "drm_shim.h"
42#include "util/hash_table.h"
43#include "util/u_atomic.h"
44
45#define SHIM_MEM_SIZE (4ull * 1024 * 1024 * 1024)
46
47#ifndef HAVE_MEMFD_CREATE
48#include <sys/syscall.h>
49
50static inline int
51memfd_create(const char *name, unsigned int flags)
52{
53   return syscall(SYS_memfd_create, name, flags);
54}
55#endif
56
57/* Global state for the shim shared between libc, core, and driver. */
58struct shim_device shim_device;
59
60long shim_page_size;
61
62static uint32_t
63uint_key_hash(const void *key)
64{
65   return (uintptr_t)key;
66}
67
68static bool
69uint_key_compare(const void *a, const void *b)
70{
71   return a == b;
72}
73
74/**
75 * Called when the first libc shim is called, to initialize GEM simulation
76 * state (other than the shims themselves).
77 */
78void
79drm_shim_device_init(void)
80{
81   shim_device.fd_map = _mesa_hash_table_create(NULL,
82                                                uint_key_hash,
83                                                uint_key_compare);
84
85   shim_device.offset_map = _mesa_hash_table_u64_create(NULL);
86
87   mtx_init(&shim_device.mem_lock, mtx_plain);
88
89   shim_device.mem_fd = memfd_create("shim mem", MFD_CLOEXEC);
90   assert(shim_device.mem_fd != -1);
91
92   ASSERTED int ret = ftruncate(shim_device.mem_fd, SHIM_MEM_SIZE);
93   assert(ret == 0);
94
95   /* The man page for mmap() says
96    *
97    *    offset must be a multiple of the page size as returned by
98    *    sysconf(_SC_PAGE_SIZE).
99    *
100    * Depending on the configuration of the kernel, this may not be 4096. Get
101    * this page size once and use it as the page size throughout, ensuring that
102    * are offsets are page-size aligned as required. Otherwise, mmap will fail
103    * with EINVAL.
104    */
105
106   shim_page_size = sysconf(_SC_PAGE_SIZE);
107
108   util_vma_heap_init(&shim_device.mem_heap, shim_page_size,
109                      SHIM_MEM_SIZE - shim_page_size);
110
111   drm_shim_driver_init();
112}
113
114static struct shim_fd *
115drm_shim_file_create(int fd)
116{
117   struct shim_fd *shim_fd = calloc(1, sizeof(*shim_fd));
118
119   shim_fd->fd = fd;
120   p_atomic_set(&shim_fd->refcount, 1);
121   mtx_init(&shim_fd->handle_lock, mtx_plain);
122   shim_fd->handles = _mesa_hash_table_create(NULL,
123                                              uint_key_hash,
124                                              uint_key_compare);
125
126   return shim_fd;
127}
128
129/**
130 * Called when the libc shims have interposed an open or dup of our simulated
131 * DRM device.
132 */
133void drm_shim_fd_register(int fd, struct shim_fd *shim_fd)
134{
135   if (!shim_fd)
136      shim_fd = drm_shim_file_create(fd);
137   else
138      p_atomic_inc(&shim_fd->refcount);
139
140   _mesa_hash_table_insert(shim_device.fd_map, (void *)(uintptr_t)(fd + 1), shim_fd);
141}
142
143static void handle_delete_fxn(struct hash_entry *entry)
144{
145   drm_shim_bo_put(entry->data);
146}
147
148void drm_shim_fd_unregister(int fd)
149{
150   struct hash_entry *entry =
151         _mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1));
152   if (!entry)
153      return;
154   struct shim_fd *shim_fd = entry->data;
155   _mesa_hash_table_remove(shim_device.fd_map, entry);
156
157   if (!p_atomic_dec_zero(&shim_fd->refcount))
158      return;
159
160   _mesa_hash_table_destroy(shim_fd->handles, handle_delete_fxn);
161   free(shim_fd);
162}
163
164struct shim_fd *
165drm_shim_fd_lookup(int fd)
166{
167   if (fd == -1)
168      return NULL;
169
170   struct hash_entry *entry =
171      _mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1));
172
173   if (!entry)
174      return NULL;
175   return entry->data;
176}
177
178/* ioctl used by drmGetVersion() */
179static int
180drm_shim_ioctl_version(int fd, unsigned long request, void *arg)
181{
182   struct drm_version *args = arg;
183   const char *date = "20190320";
184   const char *desc = "shim";
185
186   args->version_major = shim_device.version_major;
187   args->version_minor = shim_device.version_minor;
188   args->version_patchlevel = shim_device.version_patchlevel;
189
190   if (args->name)
191      strncpy(args->name, shim_device.driver_name, args->name_len);
192   if (args->date)
193      strncpy(args->date, date, args->date_len);
194   if (args->desc)
195      strncpy(args->desc, desc, args->desc_len);
196   args->name_len = strlen(shim_device.driver_name);
197   args->date_len = strlen(date);
198   args->desc_len = strlen(desc);
199
200   return 0;
201}
202
203static int
204drm_shim_ioctl_get_unique(int fd, unsigned long request, void *arg)
205{
206   struct drm_unique *gu = arg;
207
208   if (gu->unique && shim_device.unique)
209      strncpy(gu->unique, shim_device.unique, gu->unique_len);
210   gu->unique_len = shim_device.unique ? strlen(shim_device.unique) : 0;
211
212   return 0;
213}
214
215static int
216drm_shim_ioctl_get_cap(int fd, unsigned long request, void *arg)
217{
218   struct drm_get_cap *gc = arg;
219
220   switch (gc->capability) {
221   case DRM_CAP_PRIME:
222   case DRM_CAP_SYNCOBJ:
223   case DRM_CAP_SYNCOBJ_TIMELINE:
224      gc->value = 1;
225      return 0;
226
227   default:
228      fprintf(stderr, "DRM_IOCTL_GET_CAP: unhandled 0x%x\n",
229              (int)gc->capability);
230      return -1;
231   }
232}
233
234static int
235drm_shim_ioctl_gem_close(int fd, unsigned long request, void *arg)
236{
237   struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
238   struct drm_gem_close *c = arg;
239
240   if (!c->handle)
241      return 0;
242
243   mtx_lock(&shim_fd->handle_lock);
244   struct hash_entry *entry =
245      _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)c->handle);
246   if (!entry) {
247      mtx_unlock(&shim_fd->handle_lock);
248      return -EINVAL;
249   }
250
251   struct shim_bo *bo = entry->data;
252   _mesa_hash_table_remove(shim_fd->handles, entry);
253   drm_shim_bo_put(bo);
254   mtx_unlock(&shim_fd->handle_lock);
255   return 0;
256}
257
258static int
259drm_shim_ioctl_syncobj_create(int fd, unsigned long request, void *arg)
260{
261   struct drm_syncobj_create *create = arg;
262
263   create->handle = 1; /* 0 is invalid */
264
265   return 0;
266}
267
268static int
269drm_shim_ioctl_stub(int fd, unsigned long request, void *arg)
270{
271   return 0;
272}
273
274ioctl_fn_t core_ioctls[] = {
275   [_IOC_NR(DRM_IOCTL_VERSION)] = drm_shim_ioctl_version,
276   [_IOC_NR(DRM_IOCTL_GET_UNIQUE)] = drm_shim_ioctl_get_unique,
277   [_IOC_NR(DRM_IOCTL_GET_CAP)] = drm_shim_ioctl_get_cap,
278   [_IOC_NR(DRM_IOCTL_GEM_CLOSE)] = drm_shim_ioctl_gem_close,
279   [_IOC_NR(DRM_IOCTL_SYNCOBJ_CREATE)] = drm_shim_ioctl_syncobj_create,
280   [_IOC_NR(DRM_IOCTL_SYNCOBJ_DESTROY)] = drm_shim_ioctl_stub,
281   [_IOC_NR(DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD)] = drm_shim_ioctl_stub,
282   [_IOC_NR(DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE)] = drm_shim_ioctl_stub,
283   [_IOC_NR(DRM_IOCTL_SYNCOBJ_WAIT)] = drm_shim_ioctl_stub,
284};
285
286/**
287 * Implements the GEM core ioctls, and calls into driver-specific ioctls.
288 */
289int
290drm_shim_ioctl(int fd, unsigned long request, void *arg)
291{
292   ASSERTED int type = _IOC_TYPE(request);
293   int nr = _IOC_NR(request);
294
295   assert(type == DRM_IOCTL_BASE);
296
297   if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
298      int driver_nr = nr - DRM_COMMAND_BASE;
299
300      if (driver_nr < shim_device.driver_ioctl_count &&
301          shim_device.driver_ioctls[driver_nr]) {
302         return shim_device.driver_ioctls[driver_nr](fd, request, arg);
303      }
304   } else {
305      if (nr < ARRAY_SIZE(core_ioctls) && core_ioctls[nr]) {
306         return core_ioctls[nr](fd, request, arg);
307      }
308   }
309
310   if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
311      fprintf(stderr,
312              "DRM_SHIM: unhandled driver DRM ioctl %d (0x%08lx)\n",
313              nr - DRM_COMMAND_BASE, request);
314   } else {
315      fprintf(stderr,
316              "DRM_SHIM: unhandled core DRM ioctl 0x%X (0x%08lx)\n",
317              nr, request);
318   }
319
320   return -EINVAL;
321}
322
323int
324drm_shim_bo_init(struct shim_bo *bo, size_t size)
325{
326
327   mtx_lock(&shim_device.mem_lock);
328   bo->mem_addr = util_vma_heap_alloc(&shim_device.mem_heap, size, shim_page_size);
329   mtx_unlock(&shim_device.mem_lock);
330
331   if (!bo->mem_addr)
332      return -ENOMEM;
333
334   bo->size = size;
335
336   return 0;
337}
338
339struct shim_bo *
340drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle)
341{
342   if (!handle)
343      return NULL;
344
345   mtx_lock(&shim_fd->handle_lock);
346   struct hash_entry *entry =
347      _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)handle);
348   struct shim_bo *bo = entry ? entry->data : NULL;
349   mtx_unlock(&shim_fd->handle_lock);
350
351   if (bo)
352      p_atomic_inc(&bo->refcount);
353
354   return bo;
355}
356
357void
358drm_shim_bo_get(struct shim_bo *bo)
359{
360   p_atomic_inc(&bo->refcount);
361}
362
363void
364drm_shim_bo_put(struct shim_bo *bo)
365{
366   if (p_atomic_dec_return(&bo->refcount) == 0)
367      return;
368
369   if (shim_device.driver_bo_free)
370      shim_device.driver_bo_free(bo);
371
372   mtx_lock(&shim_device.mem_lock);
373   util_vma_heap_free(&shim_device.mem_heap, bo->mem_addr, bo->size);
374   mtx_unlock(&shim_device.mem_lock);
375   free(bo);
376}
377
378int
379drm_shim_bo_get_handle(struct shim_fd *shim_fd, struct shim_bo *bo)
380{
381   /* We should probably have some real datastructure for finding the free
382    * number.
383    */
384   mtx_lock(&shim_fd->handle_lock);
385   for (int new_handle = 1; ; new_handle++) {
386      void *key = (void *)(uintptr_t)new_handle;
387      if (!_mesa_hash_table_search(shim_fd->handles, key)) {
388         drm_shim_bo_get(bo);
389         _mesa_hash_table_insert(shim_fd->handles, key, bo);
390         mtx_unlock(&shim_fd->handle_lock);
391         return new_handle;
392      }
393   }
394   mtx_unlock(&shim_fd->handle_lock);
395
396   return 0;
397}
398
399/* Creates an mmap offset for the BO in the DRM fd.
400 */
401uint64_t
402drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd, struct shim_bo *bo)
403{
404   mtx_lock(&shim_device.mem_lock);
405   _mesa_hash_table_u64_insert(shim_device.offset_map, bo->mem_addr, bo);
406   mtx_unlock(&shim_device.mem_lock);
407
408   /* reuse the buffer address as the mmap offset: */
409   return bo->mem_addr;
410}
411
412/* For mmap() on the DRM fd, look up the BO from the "offset" and map the BO's
413 * fd.
414 */
415void *
416drm_shim_mmap(struct shim_fd *shim_fd, size_t length, int prot, int flags,
417              int fd, off64_t offset)
418{
419   mtx_lock(&shim_device.mem_lock);
420   struct shim_bo *bo = _mesa_hash_table_u64_search(shim_device.offset_map, offset);
421   mtx_unlock(&shim_device.mem_lock);
422
423   if (!bo)
424      return MAP_FAILED;
425
426   if (length > bo->size)
427      return MAP_FAILED;
428
429   /* The offset we pass to mmap must be aligned to the page size */
430   assert((bo->mem_addr & (shim_page_size - 1)) == 0);
431
432   return mmap(NULL, length, prot, flags, shim_device.mem_fd, bo->mem_addr);
433}
434