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