325a8a142e52756cb3335e12c1c65c7df999105a
[mesa.git] / src / drm-shim / drm_shim.c
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 /**
25 * @file
26 *
27 * Implements wrappers of libc functions to fake having a DRM device that
28 * isn't actually present in the kernel.
29 */
30
31 /* Prevent glibc from defining open64 when we want to alias it. */
32 #undef _FILE_OFFSET_BITS
33 #define _LARGEFILE64_SOURCE
34
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <sys/ioctl.h>
41 #include <sys/mman.h>
42 #include <sys/stat.h>
43 #include <sys/sysmacros.h>
44 #include <stdarg.h>
45 #include <fcntl.h>
46 #include <dlfcn.h>
47 #include <dirent.h>
48 #include <c11/threads.h>
49 #include <drm-uapi/drm.h>
50
51 #include "util/set.h"
52 #include "util/u_debug.h"
53 #include "drm_shim.h"
54
55 #define REAL_FUNCTION_POINTER(x) typeof(x) *real_##x
56
57 static mtx_t shim_lock = _MTX_INITIALIZER_NP;
58 struct set *opendir_set;
59 bool drm_shim_debug;
60
61 /* If /dev/dri doesn't exist, we'll need an arbitrary pointer that wouldn't be
62 * returned by any other opendir() call so we can return just our fake node.
63 */
64 DIR *fake_dev_dri = (void *)&opendir_set;
65
66 /* XXX: implement REAL_FUNCTION_POINTER(close); */
67 REAL_FUNCTION_POINTER(closedir);
68 REAL_FUNCTION_POINTER(dup);
69 REAL_FUNCTION_POINTER(fcntl);
70 REAL_FUNCTION_POINTER(fopen);
71 REAL_FUNCTION_POINTER(ioctl);
72 REAL_FUNCTION_POINTER(mmap);
73 REAL_FUNCTION_POINTER(open);
74 REAL_FUNCTION_POINTER(opendir);
75 REAL_FUNCTION_POINTER(readdir);
76 REAL_FUNCTION_POINTER(readdir64);
77 REAL_FUNCTION_POINTER(readlink);
78 REAL_FUNCTION_POINTER(realpath);
79 REAL_FUNCTION_POINTER(__xstat);
80 REAL_FUNCTION_POINTER(__xstat64);
81 REAL_FUNCTION_POINTER(__fxstat);
82 REAL_FUNCTION_POINTER(__fxstat64);
83
84 /* Full path of /dev/dri/renderD* */
85 static char *render_node_path;
86 /* renderD* */
87 static char *render_node_dirent_name;
88 /* /sys/dev/char/major:minor/device */
89 static char *device_path;
90 /* /sys/dev/char/major:minor/device/subsystem */
91 static char *subsystem_path;
92 int render_node_minor = -1;
93
94 struct file_override {
95 const char *path;
96 char *contents;
97 };
98 static struct file_override file_overrides[10];
99 static int file_overrides_count;
100
101 /* Come up with a filename for a render node that doesn't actually exist on
102 * the system.
103 */
104 static void
105 get_dri_render_node_minor(void)
106 {
107 for (int i = 0; i < 10; i++) {
108 int minor = 128 + i;
109 asprintf(&render_node_dirent_name, "renderD%d", minor);
110 asprintf(&render_node_path, "/dev/dri/%s",
111 render_node_dirent_name);
112 struct stat st;
113 if (stat(render_node_path, &st) == -1) {
114
115 render_node_minor = minor;
116 return;
117 }
118 }
119
120 fprintf(stderr, "Couldn't find a spare render node slot\n");
121 }
122
123 static void *get_function_pointer(const char *name)
124 {
125 void *func = dlsym(RTLD_NEXT, name);
126 if (!func) {
127 fprintf(stderr, "Failed to resolve %s\n", name);
128 abort();
129 }
130 return func;
131 }
132
133 #define GET_FUNCTION_POINTER(x) real_##x = get_function_pointer(#x)
134
135 void
136 drm_shim_override_file(const char *contents, const char *path_format, ...)
137 {
138 assert(file_overrides_count < ARRAY_SIZE(file_overrides));
139
140 char *path;
141 va_list ap;
142 va_start(ap, path_format);
143 vasprintf(&path, path_format, ap);
144 va_end(ap);
145
146 struct file_override *override = &file_overrides[file_overrides_count++];
147 override->path = path;
148 override->contents = strdup(contents);
149 }
150
151 static void
152 destroy_shim(void)
153 {
154 _mesa_set_destroy(opendir_set, NULL);
155 free(render_node_path);
156 free(render_node_dirent_name);
157 free(subsystem_path);
158 }
159
160 /* Initialization, which will be called from the first general library call
161 * that might need to be wrapped with the shim.
162 */
163 static void
164 init_shim(void)
165 {
166 static bool inited = false;
167 drm_shim_debug = debug_get_bool_option("DRM_SHIM_DEBUG", false);
168
169 /* We can't lock this, because we recurse during initialization. */
170 if (inited)
171 return;
172
173 /* This comes first (and we're locked), to make sure we don't recurse
174 * during initialization.
175 */
176 inited = true;
177
178 opendir_set = _mesa_set_create(NULL,
179 _mesa_hash_string,
180 _mesa_key_string_equal);
181
182 GET_FUNCTION_POINTER(closedir);
183 GET_FUNCTION_POINTER(dup);
184 GET_FUNCTION_POINTER(fcntl);
185 GET_FUNCTION_POINTER(fopen);
186 GET_FUNCTION_POINTER(ioctl);
187 GET_FUNCTION_POINTER(mmap);
188 GET_FUNCTION_POINTER(open);
189 GET_FUNCTION_POINTER(opendir);
190 GET_FUNCTION_POINTER(readdir);
191 GET_FUNCTION_POINTER(readdir64);
192 GET_FUNCTION_POINTER(readlink);
193 GET_FUNCTION_POINTER(realpath);
194 GET_FUNCTION_POINTER(__xstat);
195 GET_FUNCTION_POINTER(__xstat64);
196 GET_FUNCTION_POINTER(__fxstat);
197 GET_FUNCTION_POINTER(__fxstat64);
198
199 get_dri_render_node_minor();
200
201 if (drm_shim_debug) {
202 fprintf(stderr, "Initializing DRM shim on %s\n",
203 render_node_path);
204 }
205
206 asprintf(&device_path,
207 "/sys/dev/char/%d:%d/device",
208 DRM_MAJOR, render_node_minor);
209
210 asprintf(&subsystem_path,
211 "/sys/dev/char/%d:%d/device/subsystem",
212 DRM_MAJOR, render_node_minor);
213
214 drm_shim_device_init();
215
216 atexit(destroy_shim);
217 }
218
219 /* Override libdrm's reading of various sysfs files for device enumeration. */
220 PUBLIC FILE *fopen(const char *path, const char *mode)
221 {
222 init_shim();
223
224 for (int i = 0; i < file_overrides_count; i++) {
225 if (strcmp(file_overrides[i].path, path) == 0) {
226 int fds[2];
227 pipe(fds);
228 write(fds[1], file_overrides[i].contents,
229 strlen(file_overrides[i].contents));
230 close(fds[1]);
231 return fdopen(fds[0], "r");
232 }
233 }
234
235 return real_fopen(path, mode);
236 }
237 PUBLIC FILE *fopen64(const char *path, const char *mode)
238 __attribute__((alias("fopen")));
239
240 /* Intercepts open(render_node_path) to redirect it to the simulator. */
241 PUBLIC int open(const char *path, int flags, ...)
242 {
243 init_shim();
244
245 va_list ap;
246 va_start(ap, flags);
247 mode_t mode = va_arg(ap, mode_t);
248 va_end(ap);
249
250 if (strcmp(path, render_node_path) != 0)
251 return real_open(path, flags, mode);
252
253 int fd = real_open("/dev/null", O_RDWR, 0);
254
255 drm_shim_fd_register(fd, NULL);
256
257 return fd;
258 }
259 PUBLIC int open64(const char*, int, ...) __attribute__((alias("open")));
260
261 /* Fakes stat to return character device stuff for our fake render node. */
262 PUBLIC int __xstat(int ver, const char *path, struct stat *st)
263 {
264 init_shim();
265
266 /* Note: call real stat if we're in the process of probing for a free
267 * render node!
268 */
269 if (render_node_minor == -1)
270 return real___xstat(ver, path, st);
271
272 /* Fool libdrm's probe of whether the /sys dir for this char dev is
273 * there.
274 */
275 char *sys_dev_drm_dir;
276 asprintf(&sys_dev_drm_dir, "/sys/dev/char/%d:%d/device/drm",
277 DRM_MAJOR, render_node_minor);
278 if (strcmp(path, sys_dev_drm_dir) == 0) {
279 free(sys_dev_drm_dir);
280 return 0;
281 }
282 free(sys_dev_drm_dir);
283
284 if (strcmp(path, render_node_path) != 0)
285 return real___xstat(ver, path, st);
286
287 memset(st, 0, sizeof(*st));
288 st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
289 st->st_mode = S_IFCHR;
290
291 return 0;
292 }
293
294 /* Fakes stat to return character device stuff for our fake render node. */
295 PUBLIC int __xstat64(int ver, const char *path, struct stat64 *st)
296 {
297 init_shim();
298
299 /* Note: call real stat if we're in the process of probing for a free
300 * render node!
301 */
302 if (render_node_minor == -1)
303 return real___xstat64(ver, path, st);
304
305 /* Fool libdrm's probe of whether the /sys dir for this char dev is
306 * there.
307 */
308 char *sys_dev_drm_dir;
309 asprintf(&sys_dev_drm_dir, "/sys/dev/char/%d:%d/device/drm",
310 DRM_MAJOR, render_node_minor);
311 if (strcmp(path, sys_dev_drm_dir) == 0) {
312 free(sys_dev_drm_dir);
313 return 0;
314 }
315 free(sys_dev_drm_dir);
316
317 if (strcmp(path, render_node_path) != 0)
318 return real___xstat64(ver, path, st);
319
320 memset(st, 0, sizeof(*st));
321 st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
322 st->st_mode = S_IFCHR;
323
324 return 0;
325 }
326
327 /* Fakes fstat to return character device stuff for our fake render node. */
328 PUBLIC int __fxstat(int ver, int fd, struct stat *st)
329 {
330 init_shim();
331
332 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
333
334 if (!shim_fd)
335 return real___fxstat(ver, fd, st);
336
337 memset(st, 0, sizeof(*st));
338 st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
339 st->st_mode = S_IFCHR;
340
341 return 0;
342 }
343
344 PUBLIC int __fxstat64(int ver, int fd, struct stat64 *st)
345 {
346 init_shim();
347
348 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
349
350 if (!shim_fd)
351 return real___fxstat64(ver, fd, st);
352
353 memset(st, 0, sizeof(*st));
354 st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
355 st->st_mode = S_IFCHR;
356
357 return 0;
358 }
359
360 /* Tracks if the opendir was on /dev/dri. */
361 PUBLIC DIR *
362 opendir(const char *name)
363 {
364 init_shim();
365
366 DIR *dir = real_opendir(name);
367 if (strcmp(name, "/dev/dri") == 0) {
368 if (!dir) {
369 /* If /dev/dri didn't exist, we still want to be able to return our
370 * fake /dev/dri/render* even though we probably can't
371 * mkdir("/dev/dri"). Return a fake DIR pointer for that.
372 */
373 dir = fake_dev_dri;
374 }
375
376 mtx_lock(&shim_lock);
377 _mesa_set_add(opendir_set, dir);
378 mtx_unlock(&shim_lock);
379 }
380
381 return dir;
382 }
383
384 /* If we've reached the end of the real directory list and we're
385 * looking at /dev/dri, add our render node to the list.
386 */
387 PUBLIC struct dirent *
388 readdir(DIR *dir)
389 {
390 init_shim();
391
392 struct dirent *ent = NULL;
393
394 if (dir != fake_dev_dri)
395 ent = real_readdir(dir);
396 static struct dirent render_node_dirent = { 0 };
397
398 if (!ent) {
399 mtx_lock(&shim_lock);
400 if (_mesa_set_search(opendir_set, dir)) {
401 strcpy(render_node_dirent.d_name,
402 render_node_dirent_name);
403 ent = &render_node_dirent;
404 _mesa_set_remove_key(opendir_set, dir);
405 }
406 mtx_unlock(&shim_lock);
407 }
408
409 return ent;
410 }
411
412 /* If we've reached the end of the real directory list and we're
413 * looking at /dev/dri, add our render node to the list.
414 */
415 PUBLIC struct dirent64 *
416 readdir64(DIR *dir)
417 {
418 init_shim();
419
420 struct dirent64 *ent = NULL;
421 if (dir != fake_dev_dri)
422 ent = real_readdir64(dir);
423 static struct dirent64 render_node_dirent = { 0 };
424
425 if (!ent) {
426 mtx_lock(&shim_lock);
427 if (_mesa_set_search(opendir_set, dir)) {
428 strcpy(render_node_dirent.d_name,
429 render_node_dirent_name);
430 ent = &render_node_dirent;
431 _mesa_set_remove_key(opendir_set, dir);
432 }
433 mtx_unlock(&shim_lock);
434 }
435
436 return ent;
437 }
438
439 /* Cleans up tracking of opendir("/dev/dri") */
440 PUBLIC int
441 closedir(DIR *dir)
442 {
443 init_shim();
444
445 mtx_lock(&shim_lock);
446 _mesa_set_remove_key(opendir_set, dir);
447 mtx_unlock(&shim_lock);
448
449 if (dir != fake_dev_dri)
450 return real_closedir(dir);
451 else
452 return 0;
453 }
454
455 /* Handles libdrm's readlink to figure out what kind of device we have. */
456 PUBLIC ssize_t
457 readlink(const char *path, char *buf, size_t size)
458 {
459 init_shim();
460
461 if (strcmp(path, subsystem_path) != 0)
462 return real_readlink(path, buf, size);
463
464 static const struct {
465 const char *name;
466 int bus_type;
467 } bus_types[] = {
468 { "/pci", DRM_BUS_PCI },
469 { "/usb", DRM_BUS_USB },
470 { "/platform", DRM_BUS_PLATFORM },
471 { "/spi", DRM_BUS_PLATFORM },
472 { "/host1x", DRM_BUS_HOST1X },
473 };
474
475 for (uint32_t i = 0; i < ARRAY_SIZE(bus_types); i++) {
476 if (bus_types[i].bus_type != shim_device.bus_type)
477 continue;
478
479 strncpy(buf, bus_types[i].name, size);
480 buf[size - 1] = 0;
481 break;
482 }
483
484 return strlen(buf) + 1;
485 }
486
487 /* Handles libdrm's realpath to figure out what kind of device we have. */
488 PUBLIC char *
489 realpath(const char *path, char *resolved_path)
490 {
491 init_shim();
492
493 if (strcmp(path, device_path) != 0)
494 return real_realpath(path, resolved_path);
495
496 strcpy(resolved_path, path);
497
498 return resolved_path;
499 }
500
501 /* Main entrypoint to DRM drivers: the ioctl syscall. We send all ioctls on
502 * our DRM fd to drm_shim_ioctl().
503 */
504 PUBLIC int
505 ioctl(int fd, unsigned long request, ...)
506 {
507 init_shim();
508
509 va_list ap;
510 va_start(ap, request);
511 void *arg = va_arg(ap, void *);
512 va_end(ap);
513
514 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
515 if (!shim_fd)
516 return real_ioctl(fd, request, arg);
517
518 return drm_shim_ioctl(fd, request, arg);
519 }
520
521 /* Gallium uses this to dup the incoming fd on gbm screen creation */
522 PUBLIC int
523 fcntl(int fd, int cmd, ...)
524 {
525 init_shim();
526
527 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
528
529 va_list ap;
530 va_start(ap, cmd);
531 void *arg = va_arg(ap, void *);
532 va_end(ap);
533
534 int ret = real_fcntl(fd, cmd, arg);
535
536 if (shim_fd && (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC))
537 drm_shim_fd_register(ret, shim_fd);
538
539 return ret;
540 }
541 PUBLIC int fcntl64(int, int, ...)
542 __attribute__((alias("fcntl")));
543
544 /* I wrote this when trying to fix gallium screen creation, leaving it around
545 * since it's probably good to have.
546 */
547 PUBLIC int
548 dup(int fd)
549 {
550 init_shim();
551
552 int ret = real_dup(fd);
553
554 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
555 if (shim_fd && ret >= 0)
556 drm_shim_fd_register(ret, shim_fd);
557
558 return ret;
559 }
560
561 PUBLIC void *
562 mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
563 {
564 init_shim();
565
566 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
567 if (shim_fd)
568 return drm_shim_mmap(shim_fd, length, prot, flags, fd, offset);
569
570 return real_mmap(addr, length, prot, flags, fd, offset);
571 }
572 PUBLIC void *mmap64(void*, size_t, int, int, int, off_t)
573 __attribute__((alias("mmap")));