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
50 static inline int
memfd_create(const char * name,unsigned int flags)51 memfd_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. */
58 struct shim_device shim_device;
59
60 long shim_page_size;
61
62 static uint32_t
uint_key_hash(const void * key)63 uint_key_hash(const void *key)
64 {
65 return (uintptr_t)key;
66 }
67
68 static bool
uint_key_compare(const void * a,const void * b)69 uint_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 */
78 void
drm_shim_device_init(void)79 drm_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
114 static struct shim_fd *
drm_shim_file_create(int fd)115 drm_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 */
drm_shim_fd_register(int fd,struct shim_fd * shim_fd)133 void 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
handle_delete_fxn(struct hash_entry * entry)143 static void handle_delete_fxn(struct hash_entry *entry)
144 {
145 drm_shim_bo_put(entry->data);
146 }
147
drm_shim_fd_unregister(int fd)148 void drm_shim_fd_unregister(int fd)
149 {
150 if (fd == -1)
151 return;
152
153 struct hash_entry *entry =
154 _mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1));
155 if (!entry)
156 return;
157 struct shim_fd *shim_fd = entry->data;
158 _mesa_hash_table_remove(shim_device.fd_map, entry);
159
160 if (!p_atomic_dec_zero(&shim_fd->refcount))
161 return;
162
163 _mesa_hash_table_destroy(shim_fd->handles, handle_delete_fxn);
164 free(shim_fd);
165 }
166
167 struct shim_fd *
drm_shim_fd_lookup(int fd)168 drm_shim_fd_lookup(int fd)
169 {
170 if (fd == -1)
171 return NULL;
172
173 struct hash_entry *entry =
174 _mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1));
175
176 if (!entry)
177 return NULL;
178 return entry->data;
179 }
180
181 /* ioctl used by drmGetVersion() */
182 static int
drm_shim_ioctl_version(int fd,unsigned long request,void * arg)183 drm_shim_ioctl_version(int fd, unsigned long request, void *arg)
184 {
185 struct drm_version *args = arg;
186 const char *date = "20190320";
187 const char *desc = "shim";
188
189 args->version_major = shim_device.version_major;
190 args->version_minor = shim_device.version_minor;
191 args->version_patchlevel = shim_device.version_patchlevel;
192
193 if (args->name)
194 strncpy(args->name, shim_device.driver_name, args->name_len);
195 if (args->date)
196 strncpy(args->date, date, args->date_len);
197 if (args->desc)
198 strncpy(args->desc, desc, args->desc_len);
199 args->name_len = strlen(shim_device.driver_name);
200 args->date_len = strlen(date);
201 args->desc_len = strlen(desc);
202
203 return 0;
204 }
205
206 static int
drm_shim_ioctl_get_unique(int fd,unsigned long request,void * arg)207 drm_shim_ioctl_get_unique(int fd, unsigned long request, void *arg)
208 {
209 struct drm_unique *gu = arg;
210
211 if (gu->unique && shim_device.unique)
212 strncpy(gu->unique, shim_device.unique, gu->unique_len);
213 gu->unique_len = shim_device.unique ? strlen(shim_device.unique) : 0;
214
215 return 0;
216 }
217
218 static int
drm_shim_ioctl_get_cap(int fd,unsigned long request,void * arg)219 drm_shim_ioctl_get_cap(int fd, unsigned long request, void *arg)
220 {
221 struct drm_get_cap *gc = arg;
222
223 switch (gc->capability) {
224 case DRM_CAP_PRIME:
225 case DRM_CAP_SYNCOBJ:
226 case DRM_CAP_SYNCOBJ_TIMELINE:
227 gc->value = 1;
228 return 0;
229
230 default:
231 fprintf(stderr, "DRM_IOCTL_GET_CAP: unhandled 0x%x\n",
232 (int)gc->capability);
233 return -1;
234 }
235 }
236
237 static int
drm_shim_ioctl_gem_close(int fd,unsigned long request,void * arg)238 drm_shim_ioctl_gem_close(int fd, unsigned long request, void *arg)
239 {
240 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
241 struct drm_gem_close *c = arg;
242
243 if (!c->handle)
244 return 0;
245
246 mtx_lock(&shim_fd->handle_lock);
247 struct hash_entry *entry =
248 _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)c->handle);
249 if (!entry) {
250 mtx_unlock(&shim_fd->handle_lock);
251 return -EINVAL;
252 }
253
254 struct shim_bo *bo = entry->data;
255 _mesa_hash_table_remove(shim_fd->handles, entry);
256 drm_shim_bo_put(bo);
257 mtx_unlock(&shim_fd->handle_lock);
258 return 0;
259 }
260
261 static int
drm_shim_ioctl_syncobj_create(int fd,unsigned long request,void * arg)262 drm_shim_ioctl_syncobj_create(int fd, unsigned long request, void *arg)
263 {
264 struct drm_syncobj_create *create = arg;
265
266 create->handle = 1; /* 0 is invalid */
267
268 return 0;
269 }
270
271 static int
drm_shim_ioctl_stub(int fd,unsigned long request,void * arg)272 drm_shim_ioctl_stub(int fd, unsigned long request, void *arg)
273 {
274 return 0;
275 }
276
277 ioctl_fn_t core_ioctls[] = {
278 [_IOC_NR(DRM_IOCTL_VERSION)] = drm_shim_ioctl_version,
279 [_IOC_NR(DRM_IOCTL_GET_UNIQUE)] = drm_shim_ioctl_get_unique,
280 [_IOC_NR(DRM_IOCTL_GET_CAP)] = drm_shim_ioctl_get_cap,
281 [_IOC_NR(DRM_IOCTL_GEM_CLOSE)] = drm_shim_ioctl_gem_close,
282 [_IOC_NR(DRM_IOCTL_SYNCOBJ_CREATE)] = drm_shim_ioctl_syncobj_create,
283 [_IOC_NR(DRM_IOCTL_SYNCOBJ_DESTROY)] = drm_shim_ioctl_stub,
284 [_IOC_NR(DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD)] = drm_shim_ioctl_stub,
285 [_IOC_NR(DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE)] = drm_shim_ioctl_stub,
286 [_IOC_NR(DRM_IOCTL_SYNCOBJ_WAIT)] = drm_shim_ioctl_stub,
287 [_IOC_NR(DRM_IOCTL_SYNCOBJ_TRANSFER)] = drm_shim_ioctl_stub,
288 };
289
290 /**
291 * Implements the GEM core ioctls, and calls into driver-specific ioctls.
292 */
293 int
drm_shim_ioctl(int fd,unsigned long request,void * arg)294 drm_shim_ioctl(int fd, unsigned long request, void *arg)
295 {
296 ASSERTED int type = _IOC_TYPE(request);
297 int nr = _IOC_NR(request);
298
299 assert(type == DRM_IOCTL_BASE);
300
301 if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
302 int driver_nr = nr - DRM_COMMAND_BASE;
303
304 if (driver_nr < shim_device.driver_ioctl_count &&
305 shim_device.driver_ioctls[driver_nr]) {
306 return shim_device.driver_ioctls[driver_nr](fd, request, arg);
307 }
308 } else {
309 if (nr < ARRAY_SIZE(core_ioctls) && core_ioctls[nr]) {
310 return core_ioctls[nr](fd, request, arg);
311 }
312 }
313
314 if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
315 fprintf(stderr,
316 "DRM_SHIM: unhandled driver DRM ioctl %d (0x%08lx)\n",
317 nr - DRM_COMMAND_BASE, request);
318 } else {
319 fprintf(stderr,
320 "DRM_SHIM: unhandled core DRM ioctl 0x%X (0x%08lx)\n",
321 nr, request);
322 }
323
324 return -EINVAL;
325 }
326
327 int
drm_shim_bo_init(struct shim_bo * bo,size_t size)328 drm_shim_bo_init(struct shim_bo *bo, size_t size)
329 {
330
331 mtx_lock(&shim_device.mem_lock);
332 bo->mem_addr = util_vma_heap_alloc(&shim_device.mem_heap, size, shim_page_size);
333 mtx_unlock(&shim_device.mem_lock);
334
335 if (!bo->mem_addr)
336 return -ENOMEM;
337
338 bo->size = size;
339
340 return 0;
341 }
342
343 struct shim_bo *
drm_shim_bo_lookup(struct shim_fd * shim_fd,int handle)344 drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle)
345 {
346 if (!handle)
347 return NULL;
348
349 mtx_lock(&shim_fd->handle_lock);
350 struct hash_entry *entry =
351 _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)handle);
352 struct shim_bo *bo = entry ? entry->data : NULL;
353 mtx_unlock(&shim_fd->handle_lock);
354
355 if (bo)
356 p_atomic_inc(&bo->refcount);
357
358 return bo;
359 }
360
361 void
drm_shim_bo_get(struct shim_bo * bo)362 drm_shim_bo_get(struct shim_bo *bo)
363 {
364 p_atomic_inc(&bo->refcount);
365 }
366
367 void
drm_shim_bo_put(struct shim_bo * bo)368 drm_shim_bo_put(struct shim_bo *bo)
369 {
370 if (p_atomic_dec_return(&bo->refcount) == 0)
371 return;
372
373 if (shim_device.driver_bo_free)
374 shim_device.driver_bo_free(bo);
375
376 mtx_lock(&shim_device.mem_lock);
377 util_vma_heap_free(&shim_device.mem_heap, bo->mem_addr, bo->size);
378 mtx_unlock(&shim_device.mem_lock);
379 free(bo);
380 }
381
382 int
drm_shim_bo_get_handle(struct shim_fd * shim_fd,struct shim_bo * bo)383 drm_shim_bo_get_handle(struct shim_fd *shim_fd, struct shim_bo *bo)
384 {
385 /* We should probably have some real datastructure for finding the free
386 * number.
387 */
388 mtx_lock(&shim_fd->handle_lock);
389 for (int new_handle = 1; ; new_handle++) {
390 void *key = (void *)(uintptr_t)new_handle;
391 if (!_mesa_hash_table_search(shim_fd->handles, key)) {
392 drm_shim_bo_get(bo);
393 _mesa_hash_table_insert(shim_fd->handles, key, bo);
394 mtx_unlock(&shim_fd->handle_lock);
395 return new_handle;
396 }
397 }
398 mtx_unlock(&shim_fd->handle_lock);
399
400 return 0;
401 }
402
403 /* Creates an mmap offset for the BO in the DRM fd.
404 */
405 uint64_t
drm_shim_bo_get_mmap_offset(struct shim_fd * shim_fd,struct shim_bo * bo)406 drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd, struct shim_bo *bo)
407 {
408 mtx_lock(&shim_device.mem_lock);
409 _mesa_hash_table_u64_insert(shim_device.offset_map, bo->mem_addr, bo);
410 mtx_unlock(&shim_device.mem_lock);
411
412 /* reuse the buffer address as the mmap offset: */
413 return bo->mem_addr;
414 }
415
416 /* For mmap() on the DRM fd, look up the BO from the "offset" and map the BO's
417 * fd.
418 */
419 void *
drm_shim_mmap(struct shim_fd * shim_fd,size_t length,int prot,int flags,int fd,off64_t offset)420 drm_shim_mmap(struct shim_fd *shim_fd, size_t length, int prot, int flags,
421 int fd, off64_t offset)
422 {
423 mtx_lock(&shim_device.mem_lock);
424 struct shim_bo *bo = _mesa_hash_table_u64_search(shim_device.offset_map, offset);
425 mtx_unlock(&shim_device.mem_lock);
426
427 if (!bo)
428 return MAP_FAILED;
429
430 if (length > bo->size)
431 return MAP_FAILED;
432
433 /* The offset we pass to mmap must be aligned to the page size */
434 assert((bo->mem_addr & (shim_page_size - 1)) == 0);
435
436 return mmap(NULL, length, prot, flags, shim_device.mem_fd, bo->mem_addr);
437 }
438