device.c revision 7ec681f3
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   mtx_init(&shim_device.mem_lock, mtx_plain);
86
87   shim_device.mem_fd = memfd_create("shim mem", MFD_CLOEXEC);
88   assert(shim_device.mem_fd != -1);
89
90   ASSERTED int ret = ftruncate(shim_device.mem_fd, SHIM_MEM_SIZE);
91   assert(ret == 0);
92
93   /* The man page for mmap() says
94    *
95    *    offset must be a multiple of the page size as returned by
96    *    sysconf(_SC_PAGE_SIZE).
97    *
98    * Depending on the configuration of the kernel, this may not be 4096. Get
99    * this page size once and use it as the page size throughout, ensuring that
100    * are offsets are page-size aligned as required. Otherwise, mmap will fail
101    * with EINVAL.
102    */
103
104   shim_page_size = sysconf(_SC_PAGE_SIZE);
105
106   util_vma_heap_init(&shim_device.mem_heap, shim_page_size,
107                      SHIM_MEM_SIZE - shim_page_size);
108
109   drm_shim_driver_init();
110}
111
112static struct shim_fd *
113drm_shim_file_create(int fd)
114{
115   struct shim_fd *shim_fd = calloc(1, sizeof(*shim_fd));
116
117   shim_fd->fd = fd;
118   mtx_init(&shim_fd->handle_lock, mtx_plain);
119   shim_fd->handles = _mesa_hash_table_create(NULL,
120                                              uint_key_hash,
121                                              uint_key_compare);
122
123   return shim_fd;
124}
125
126/**
127 * Called when the libc shims have interposed an open or dup of our simulated
128 * DRM device.
129 */
130void drm_shim_fd_register(int fd, struct shim_fd *shim_fd)
131{
132   if (!shim_fd)
133      shim_fd = drm_shim_file_create(fd);
134
135   _mesa_hash_table_insert(shim_device.fd_map, (void *)(uintptr_t)(fd + 1), shim_fd);
136}
137
138struct shim_fd *
139drm_shim_fd_lookup(int fd)
140{
141   if (fd == -1)
142      return NULL;
143
144   struct hash_entry *entry =
145      _mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1));
146
147   if (!entry)
148      return NULL;
149   return entry->data;
150}
151
152/* ioctl used by drmGetVersion() */
153static int
154drm_shim_ioctl_version(int fd, unsigned long request, void *arg)
155{
156   struct drm_version *args = arg;
157   const char *date = "20190320";
158   const char *desc = "shim";
159
160   args->version_major = shim_device.version_major;
161   args->version_minor = shim_device.version_minor;
162   args->version_patchlevel = shim_device.version_patchlevel;
163
164   if (args->name)
165      strncpy(args->name, shim_device.driver_name, args->name_len);
166   if (args->date)
167      strncpy(args->date, date, args->date_len);
168   if (args->desc)
169      strncpy(args->desc, desc, args->desc_len);
170   args->name_len = strlen(shim_device.driver_name);
171   args->date_len = strlen(date);
172   args->desc_len = strlen(desc);
173
174   return 0;
175}
176
177static int
178drm_shim_ioctl_get_cap(int fd, unsigned long request, void *arg)
179{
180   struct drm_get_cap *gc = arg;
181
182   switch (gc->capability) {
183   case DRM_CAP_PRIME:
184   case DRM_CAP_SYNCOBJ:
185   case DRM_CAP_SYNCOBJ_TIMELINE:
186      gc->value = 1;
187      return 0;
188
189   default:
190      fprintf(stderr, "DRM_IOCTL_GET_CAP: unhandled 0x%x\n",
191              (int)gc->capability);
192      return -1;
193   }
194}
195
196static int
197drm_shim_ioctl_gem_close(int fd, unsigned long request, void *arg)
198{
199   struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
200   struct drm_gem_close *c = arg;
201
202   if (!c->handle)
203      return 0;
204
205   mtx_lock(&shim_fd->handle_lock);
206   struct hash_entry *entry =
207      _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)c->handle);
208   if (!entry) {
209      mtx_unlock(&shim_fd->handle_lock);
210      return -EINVAL;
211   }
212
213   struct shim_bo *bo = entry->data;
214   _mesa_hash_table_remove(shim_fd->handles, entry);
215   drm_shim_bo_put(bo);
216   mtx_unlock(&shim_fd->handle_lock);
217   return 0;
218}
219
220static int
221drm_shim_ioctl_syncobj_create(int fd, unsigned long request, void *arg)
222{
223   struct drm_syncobj_create *create = arg;
224
225   create->handle = 1; /* 0 is invalid */
226
227   return 0;
228}
229
230static int
231drm_shim_ioctl_stub(int fd, unsigned long request, void *arg)
232{
233   return 0;
234}
235
236ioctl_fn_t core_ioctls[] = {
237   [_IOC_NR(DRM_IOCTL_VERSION)] = drm_shim_ioctl_version,
238   [_IOC_NR(DRM_IOCTL_GET_CAP)] = drm_shim_ioctl_get_cap,
239   [_IOC_NR(DRM_IOCTL_GEM_CLOSE)] = drm_shim_ioctl_gem_close,
240   [_IOC_NR(DRM_IOCTL_SYNCOBJ_CREATE)] = drm_shim_ioctl_syncobj_create,
241   [_IOC_NR(DRM_IOCTL_SYNCOBJ_DESTROY)] = drm_shim_ioctl_stub,
242   [_IOC_NR(DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD)] = drm_shim_ioctl_stub,
243   [_IOC_NR(DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE)] = drm_shim_ioctl_stub,
244   [_IOC_NR(DRM_IOCTL_SYNCOBJ_WAIT)] = drm_shim_ioctl_stub,
245};
246
247/**
248 * Implements the GEM core ioctls, and calls into driver-specific ioctls.
249 */
250int
251drm_shim_ioctl(int fd, unsigned long request, void *arg)
252{
253   ASSERTED int type = _IOC_TYPE(request);
254   int nr = _IOC_NR(request);
255
256   assert(type == DRM_IOCTL_BASE);
257
258   if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
259      int driver_nr = nr - DRM_COMMAND_BASE;
260
261      if (driver_nr < shim_device.driver_ioctl_count &&
262          shim_device.driver_ioctls[driver_nr]) {
263         return shim_device.driver_ioctls[driver_nr](fd, request, arg);
264      }
265   } else {
266      if (nr < ARRAY_SIZE(core_ioctls) && core_ioctls[nr]) {
267         return core_ioctls[nr](fd, request, arg);
268      }
269   }
270
271   if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
272      fprintf(stderr,
273              "DRM_SHIM: unhandled driver DRM ioctl %d (0x%08lx)\n",
274              nr - DRM_COMMAND_BASE, request);
275   } else {
276      fprintf(stderr,
277              "DRM_SHIM: unhandled core DRM ioctl 0x%X (0x%08lx)\n",
278              nr, request);
279   }
280
281   return -EINVAL;
282}
283
284void
285drm_shim_bo_init(struct shim_bo *bo, size_t size)
286{
287
288   mtx_lock(&shim_device.mem_lock);
289   bo->mem_addr = util_vma_heap_alloc(&shim_device.mem_heap, size, shim_page_size);
290   mtx_unlock(&shim_device.mem_lock);
291   assert(bo->mem_addr);
292
293   bo->size = size;
294}
295
296struct shim_bo *
297drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle)
298{
299   if (!handle)
300      return NULL;
301
302   mtx_lock(&shim_fd->handle_lock);
303   struct hash_entry *entry =
304      _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)handle);
305   struct shim_bo *bo = entry ? entry->data : NULL;
306   mtx_unlock(&shim_fd->handle_lock);
307
308   if (bo)
309      p_atomic_inc(&bo->refcount);
310
311   return bo;
312}
313
314void
315drm_shim_bo_get(struct shim_bo *bo)
316{
317   p_atomic_inc(&bo->refcount);
318}
319
320void
321drm_shim_bo_put(struct shim_bo *bo)
322{
323   if (p_atomic_dec_return(&bo->refcount) == 0)
324      return;
325
326   if (shim_device.driver_bo_free)
327      shim_device.driver_bo_free(bo);
328
329   mtx_lock(&shim_device.mem_lock);
330   util_vma_heap_free(&shim_device.mem_heap, bo->mem_addr, bo->size);
331   mtx_unlock(&shim_device.mem_lock);
332   free(bo);
333}
334
335int
336drm_shim_bo_get_handle(struct shim_fd *shim_fd, struct shim_bo *bo)
337{
338   /* We should probably have some real datastructure for finding the free
339    * number.
340    */
341   mtx_lock(&shim_fd->handle_lock);
342   for (int new_handle = 1; ; new_handle++) {
343      void *key = (void *)(uintptr_t)new_handle;
344      if (!_mesa_hash_table_search(shim_fd->handles, key)) {
345         drm_shim_bo_get(bo);
346         _mesa_hash_table_insert(shim_fd->handles, key, bo);
347         mtx_unlock(&shim_fd->handle_lock);
348         return new_handle;
349      }
350   }
351   mtx_unlock(&shim_fd->handle_lock);
352
353   return 0;
354}
355
356/* Creates an mmap offset for the BO in the DRM fd.
357 *
358 * XXX: We should be maintaining a u_mm allocator where the mmap offsets
359 * allocate the size of the BO and it can be used to look the BO back up.
360 * Instead, we just stuff the shim's pointer as the return value, and treat
361 * the incoming mmap offset on the DRM fd as a BO pointer.  This doesn't work
362 * if someone tries to map a subset of the BO, but it's enough to get V3D
363 * working for now.
364 */
365uint64_t
366drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd, struct shim_bo *bo)
367{
368   return (uintptr_t)bo;
369}
370
371/* For mmap() on the DRM fd, look up the BO from the "offset" and map the BO's
372 * fd.
373 */
374void *
375drm_shim_mmap(struct shim_fd *shim_fd, size_t length, int prot, int flags,
376              int fd, off_t offset)
377{
378   struct shim_bo *bo = (void *)(uintptr_t)offset;
379
380   /* The offset we pass to mmap must be aligned to the page size */
381   assert((bo->mem_addr & (shim_page_size - 1)) == 0);
382
383   return mmap(NULL, length, prot, flags, shim_device.mem_fd, bo->mem_addr);
384}
385