2 * Copyright © 2018 Broadcom
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:
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
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.
27 * Implements wrappers of libc functions to fake having a DRM device that
28 * isn't actually present in the kernel.
31 /* Prevent glibc from defining open64 when we want to alias it. */
32 #undef _FILE_OFFSET_BITS
33 #define _LARGEFILE64_SOURCE
40 #include <sys/ioctl.h>
43 #include <sys/sysmacros.h>
48 #include <c11/threads.h>
49 #include <drm-uapi/drm.h>
52 #include "util/u_debug.h"
55 #define REAL_FUNCTION_POINTER(x) typeof(x) *real_##x
57 static mtx_t shim_lock = _MTX_INITIALIZER_NP;
58 struct set *opendir_set;
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.
64 DIR *fake_dev_dri = (void *)&opendir_set;
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);
80 #if __GLIBC__ == 2 && __GLIBC_MINOR__ < 33
81 REAL_FUNCTION_POINTER(__xstat);
82 REAL_FUNCTION_POINTER(__xstat64);
83 REAL_FUNCTION_POINTER(__fxstat);
84 REAL_FUNCTION_POINTER(__fxstat64);
87 /* Full path of /dev/dri/renderD* */
88 static char *render_node_path;
90 static char *render_node_dirent_name;
91 /* /sys/dev/char/major:minor/device */
92 static char *device_path;
93 /* /sys/dev/char/major:minor/device/subsystem */
94 static char *subsystem_path;
95 int render_node_minor = -1;
97 struct file_override {
101 static struct file_override file_overrides[10];
102 static int file_overrides_count;
103 extern bool drm_shim_driver_prefers_first_render_node;
105 #define nfasprintf(...) \
107 UNUSED int __ret = asprintf(__VA_ARGS__); \
108 assert(__ret >= 0); \
110 #define nfvasprintf(...) \
112 UNUSED int __ret = vasprintf(__VA_ARGS__); \
113 assert(__ret >= 0); \
116 /* Pick the minor and filename for our shimmed render node. This can be
117 * either a new one that didn't exist on the system, or if the driver wants,
118 * it can replace the first render node.
121 get_dri_render_node_minor(void)
123 for (int i = 0; i < 10; i++) {
124 UNUSED int minor = 128 + i;
125 nfasprintf(&render_node_dirent_name, "renderD%d", minor);
126 nfasprintf(&render_node_path, "/dev/dri/%s",
127 render_node_dirent_name);
129 if (drm_shim_driver_prefers_first_render_node ||
130 stat(render_node_path, &st) == -1) {
132 render_node_minor = minor;
137 fprintf(stderr, "Couldn't find a spare render node slot\n");
140 static void *get_function_pointer(const char *name)
142 void *func = dlsym(RTLD_NEXT, name);
144 fprintf(stderr, "Failed to resolve %s\n", name);
150 #define GET_FUNCTION_POINTER(x) real_##x = get_function_pointer(#x)
153 drm_shim_override_file(const char *contents, const char *path_format, ...)
155 assert(file_overrides_count < ARRAY_SIZE(file_overrides));
159 va_start(ap, path_format);
160 nfvasprintf(&path, path_format, ap);
163 struct file_override *override = &file_overrides[file_overrides_count++];
164 override->path = path;
165 override->contents = strdup(contents);
171 _mesa_set_destroy(opendir_set, NULL);
172 free(render_node_path);
173 free(render_node_dirent_name);
174 free(subsystem_path);
177 /* Initialization, which will be called from the first general library call
178 * that might need to be wrapped with the shim.
183 static bool inited = false;
184 drm_shim_debug = debug_get_bool_option("DRM_SHIM_DEBUG", false);
186 /* We can't lock this, because we recurse during initialization. */
190 /* This comes first (and we're locked), to make sure we don't recurse
191 * during initialization.
195 opendir_set = _mesa_set_create(NULL,
197 _mesa_key_string_equal);
199 GET_FUNCTION_POINTER(closedir);
200 GET_FUNCTION_POINTER(dup);
201 GET_FUNCTION_POINTER(fcntl);
202 GET_FUNCTION_POINTER(fopen);
203 GET_FUNCTION_POINTER(ioctl);
204 GET_FUNCTION_POINTER(mmap);
205 GET_FUNCTION_POINTER(open);
206 GET_FUNCTION_POINTER(opendir);
207 GET_FUNCTION_POINTER(readdir);
208 GET_FUNCTION_POINTER(readdir64);
209 GET_FUNCTION_POINTER(readlink);
210 GET_FUNCTION_POINTER(realpath);
212 #if __GLIBC__ == 2 && __GLIBC_MINOR__ < 33
213 GET_FUNCTION_POINTER(__xstat);
214 GET_FUNCTION_POINTER(__xstat64);
215 GET_FUNCTION_POINTER(__fxstat);
216 GET_FUNCTION_POINTER(__fxstat64);
219 get_dri_render_node_minor();
221 if (drm_shim_debug) {
222 fprintf(stderr, "Initializing DRM shim on %s\n",
226 nfasprintf(&device_path,
227 "/sys/dev/char/%d:%d/device",
228 DRM_MAJOR, render_node_minor);
230 nfasprintf(&subsystem_path,
231 "/sys/dev/char/%d:%d/device/subsystem",
232 DRM_MAJOR, render_node_minor);
234 drm_shim_device_init();
236 atexit(destroy_shim);
239 /* Override libdrm's reading of various sysfs files for device enumeration. */
240 PUBLIC FILE *fopen(const char *path, const char *mode)
244 for (int i = 0; i < file_overrides_count; i++) {
245 if (strcmp(file_overrides[i].path, path) == 0) {
248 write(fds[1], file_overrides[i].contents,
249 strlen(file_overrides[i].contents));
251 return fdopen(fds[0], "r");
255 return real_fopen(path, mode);
257 PUBLIC FILE *fopen64(const char *path, const char *mode)
258 __attribute__((alias("fopen")));
260 /* Intercepts open(render_node_path) to redirect it to the simulator. */
261 PUBLIC int open(const char *path, int flags, ...)
267 mode_t mode = va_arg(ap, mode_t);
270 if (strcmp(path, render_node_path) != 0)
271 return real_open(path, flags, mode);
273 int fd = real_open("/dev/null", O_RDWR, 0);
275 drm_shim_fd_register(fd, NULL);
279 PUBLIC int open64(const char*, int, ...) __attribute__((alias("open")));
281 #if __GLIBC__ == 2 && __GLIBC_MINOR__ < 33
282 /* Fakes stat to return character device stuff for our fake render node. */
283 PUBLIC int __xstat(int ver, const char *path, struct stat *st)
287 /* Note: call real stat if we're in the process of probing for a free
290 if (render_node_minor == -1)
291 return real___xstat(ver, path, st);
293 /* Fool libdrm's probe of whether the /sys dir for this char dev is
296 char *sys_dev_drm_dir;
297 nfasprintf(&sys_dev_drm_dir,
298 "/sys/dev/char/%d:%d/device/drm",
299 DRM_MAJOR, render_node_minor);
300 if (strcmp(path, sys_dev_drm_dir) == 0) {
301 free(sys_dev_drm_dir);
304 free(sys_dev_drm_dir);
306 if (strcmp(path, render_node_path) != 0)
307 return real___xstat(ver, path, st);
309 memset(st, 0, sizeof(*st));
310 st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
311 st->st_mode = S_IFCHR;
316 /* Fakes stat to return character device stuff for our fake render node. */
317 PUBLIC int __xstat64(int ver, const char *path, struct stat64 *st)
321 /* Note: call real stat if we're in the process of probing for a free
324 if (render_node_minor == -1)
325 return real___xstat64(ver, path, st);
327 /* Fool libdrm's probe of whether the /sys dir for this char dev is
330 char *sys_dev_drm_dir;
331 nfasprintf(&sys_dev_drm_dir,
332 "/sys/dev/char/%d:%d/device/drm",
333 DRM_MAJOR, render_node_minor);
334 if (strcmp(path, sys_dev_drm_dir) == 0) {
335 free(sys_dev_drm_dir);
338 free(sys_dev_drm_dir);
340 if (strcmp(path, render_node_path) != 0)
341 return real___xstat64(ver, path, st);
343 memset(st, 0, sizeof(*st));
344 st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
345 st->st_mode = S_IFCHR;
350 /* Fakes fstat to return character device stuff for our fake render node. */
351 PUBLIC int __fxstat(int ver, int fd, struct stat *st)
355 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
358 return real___fxstat(ver, fd, st);
360 memset(st, 0, sizeof(*st));
361 st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
362 st->st_mode = S_IFCHR;
367 PUBLIC int __fxstat64(int ver, int fd, struct stat64 *st)
371 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
374 return real___fxstat64(ver, fd, st);
376 memset(st, 0, sizeof(*st));
377 st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
378 st->st_mode = S_IFCHR;
384 /* Tracks if the opendir was on /dev/dri. */
386 opendir(const char *name)
390 DIR *dir = real_opendir(name);
391 if (strcmp(name, "/dev/dri") == 0) {
393 /* If /dev/dri didn't exist, we still want to be able to return our
394 * fake /dev/dri/render* even though we probably can't
395 * mkdir("/dev/dri"). Return a fake DIR pointer for that.
400 mtx_lock(&shim_lock);
401 _mesa_set_add(opendir_set, dir);
402 mtx_unlock(&shim_lock);
408 /* If we've reached the end of the real directory list and we're
409 * looking at /dev/dri, add our render node to the list.
411 PUBLIC struct dirent *
416 struct dirent *ent = NULL;
418 if (dir != fake_dev_dri)
419 ent = real_readdir(dir);
420 static struct dirent render_node_dirent = { 0 };
423 mtx_lock(&shim_lock);
424 if (_mesa_set_search(opendir_set, dir)) {
425 strcpy(render_node_dirent.d_name,
426 render_node_dirent_name);
427 ent = &render_node_dirent;
428 _mesa_set_remove_key(opendir_set, dir);
430 mtx_unlock(&shim_lock);
436 /* If we've reached the end of the real directory list and we're
437 * looking at /dev/dri, add our render node to the list.
439 PUBLIC struct dirent64 *
444 struct dirent64 *ent = NULL;
445 if (dir != fake_dev_dri)
446 ent = real_readdir64(dir);
447 static struct dirent64 render_node_dirent = { 0 };
450 mtx_lock(&shim_lock);
451 if (_mesa_set_search(opendir_set, dir)) {
452 strcpy(render_node_dirent.d_name,
453 render_node_dirent_name);
454 ent = &render_node_dirent;
455 _mesa_set_remove_key(opendir_set, dir);
457 mtx_unlock(&shim_lock);
463 /* Cleans up tracking of opendir("/dev/dri") */
469 mtx_lock(&shim_lock);
470 _mesa_set_remove_key(opendir_set, dir);
471 mtx_unlock(&shim_lock);
473 if (dir != fake_dev_dri)
474 return real_closedir(dir);
479 /* Handles libdrm's readlink to figure out what kind of device we have. */
481 readlink(const char *path, char *buf, size_t size)
485 if (strcmp(path, subsystem_path) != 0)
486 return real_readlink(path, buf, size);
488 static const struct {
492 { "/pci", DRM_BUS_PCI },
493 { "/usb", DRM_BUS_USB },
494 { "/platform", DRM_BUS_PLATFORM },
495 { "/spi", DRM_BUS_PLATFORM },
496 { "/host1x", DRM_BUS_HOST1X },
499 for (uint32_t i = 0; i < ARRAY_SIZE(bus_types); i++) {
500 if (bus_types[i].bus_type != shim_device.bus_type)
503 strncpy(buf, bus_types[i].name, size);
508 return strlen(buf) + 1;
511 /* Handles libdrm's realpath to figure out what kind of device we have. */
513 realpath(const char *path, char *resolved_path)
517 if (strcmp(path, device_path) != 0)
518 return real_realpath(path, resolved_path);
520 strcpy(resolved_path, path);
522 return resolved_path;
525 /* Main entrypoint to DRM drivers: the ioctl syscall. We send all ioctls on
526 * our DRM fd to drm_shim_ioctl().
529 ioctl(int fd, unsigned long request, ...)
534 va_start(ap, request);
535 void *arg = va_arg(ap, void *);
538 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
540 return real_ioctl(fd, request, arg);
542 return drm_shim_ioctl(fd, request, arg);
545 /* Gallium uses this to dup the incoming fd on gbm screen creation */
547 fcntl(int fd, int cmd, ...)
551 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
555 void *arg = va_arg(ap, void *);
558 int ret = real_fcntl(fd, cmd, arg);
560 if (shim_fd && (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC))
561 drm_shim_fd_register(ret, shim_fd);
565 PUBLIC int fcntl64(int, int, ...)
566 __attribute__((alias("fcntl")));
568 /* I wrote this when trying to fix gallium screen creation, leaving it around
569 * since it's probably good to have.
576 int ret = real_dup(fd);
578 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
579 if (shim_fd && ret >= 0)
580 drm_shim_fd_register(ret, shim_fd);
586 mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
590 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
592 return drm_shim_mmap(shim_fd, length, prot, flags, fd, offset);
594 return real_mmap(addr, length, prot, flags, fd, offset);
596 PUBLIC void *mmap64(void*, size_t, int, int, int, off_t)
597 __attribute__((alias("mmap")));