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
);
79 REAL_FUNCTION_POINTER(__xstat
);
80 REAL_FUNCTION_POINTER(__xstat64
);
81 REAL_FUNCTION_POINTER(__fxstat
);
82 REAL_FUNCTION_POINTER(__fxstat64
);
84 /* Full path of /dev/dri/renderD* */
85 static char *render_node_path
;
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;
94 struct file_override
{
98 static struct file_override file_overrides
[10];
99 static int file_overrides_count
;
100 extern bool drm_shim_driver_prefers_first_render_node
;
102 #define nfasprintf(...) \
104 UNUSED int __ret = asprintf(__VA_ARGS__); \
105 assert(__ret >= 0); \
107 #define nfvasprintf(...) \
109 UNUSED int __ret = vasprintf(__VA_ARGS__); \
110 assert(__ret >= 0); \
113 /* Pick the minor and filename for our shimmed render node. This can be
114 * either a new one that didn't exist on the system, or if the driver wants,
115 * it can replace the first render node.
118 get_dri_render_node_minor(void)
120 for (int i
= 0; i
< 10; i
++) {
121 UNUSED
int minor
= 128 + i
;
122 nfasprintf(&render_node_dirent_name
, "renderD%d", minor
);
123 nfasprintf(&render_node_path
, "/dev/dri/%s",
124 render_node_dirent_name
);
126 if (drm_shim_driver_prefers_first_render_node
||
127 stat(render_node_path
, &st
) == -1) {
129 render_node_minor
= minor
;
134 fprintf(stderr
, "Couldn't find a spare render node slot\n");
137 static void *get_function_pointer(const char *name
)
139 void *func
= dlsym(RTLD_NEXT
, name
);
141 fprintf(stderr
, "Failed to resolve %s\n", name
);
147 #define GET_FUNCTION_POINTER(x) real_##x = get_function_pointer(#x)
150 drm_shim_override_file(const char *contents
, const char *path_format
, ...)
152 assert(file_overrides_count
< ARRAY_SIZE(file_overrides
));
156 va_start(ap
, path_format
);
157 nfvasprintf(&path
, path_format
, ap
);
160 struct file_override
*override
= &file_overrides
[file_overrides_count
++];
161 override
->path
= path
;
162 override
->contents
= strdup(contents
);
168 _mesa_set_destroy(opendir_set
, NULL
);
169 free(render_node_path
);
170 free(render_node_dirent_name
);
171 free(subsystem_path
);
174 /* Initialization, which will be called from the first general library call
175 * that might need to be wrapped with the shim.
180 static bool inited
= false;
181 drm_shim_debug
= debug_get_bool_option("DRM_SHIM_DEBUG", false);
183 /* We can't lock this, because we recurse during initialization. */
187 /* This comes first (and we're locked), to make sure we don't recurse
188 * during initialization.
192 opendir_set
= _mesa_set_create(NULL
,
194 _mesa_key_string_equal
);
196 GET_FUNCTION_POINTER(closedir
);
197 GET_FUNCTION_POINTER(dup
);
198 GET_FUNCTION_POINTER(fcntl
);
199 GET_FUNCTION_POINTER(fopen
);
200 GET_FUNCTION_POINTER(ioctl
);
201 GET_FUNCTION_POINTER(mmap
);
202 GET_FUNCTION_POINTER(open
);
203 GET_FUNCTION_POINTER(opendir
);
204 GET_FUNCTION_POINTER(readdir
);
205 GET_FUNCTION_POINTER(readdir64
);
206 GET_FUNCTION_POINTER(readlink
);
207 GET_FUNCTION_POINTER(realpath
);
208 GET_FUNCTION_POINTER(__xstat
);
209 GET_FUNCTION_POINTER(__xstat64
);
210 GET_FUNCTION_POINTER(__fxstat
);
211 GET_FUNCTION_POINTER(__fxstat64
);
213 get_dri_render_node_minor();
215 if (drm_shim_debug
) {
216 fprintf(stderr
, "Initializing DRM shim on %s\n",
220 nfasprintf(&device_path
,
221 "/sys/dev/char/%d:%d/device",
222 DRM_MAJOR
, render_node_minor
);
224 nfasprintf(&subsystem_path
,
225 "/sys/dev/char/%d:%d/device/subsystem",
226 DRM_MAJOR
, render_node_minor
);
228 drm_shim_device_init();
230 atexit(destroy_shim
);
233 /* Override libdrm's reading of various sysfs files for device enumeration. */
234 PUBLIC
FILE *fopen(const char *path
, const char *mode
)
238 for (int i
= 0; i
< file_overrides_count
; i
++) {
239 if (strcmp(file_overrides
[i
].path
, path
) == 0) {
242 write(fds
[1], file_overrides
[i
].contents
,
243 strlen(file_overrides
[i
].contents
));
245 return fdopen(fds
[0], "r");
249 return real_fopen(path
, mode
);
251 PUBLIC
FILE *fopen64(const char *path
, const char *mode
)
252 __attribute__((alias("fopen")));
254 /* Intercepts open(render_node_path) to redirect it to the simulator. */
255 PUBLIC
int open(const char *path
, int flags
, ...)
261 mode_t mode
= va_arg(ap
, mode_t
);
264 if (strcmp(path
, render_node_path
) != 0)
265 return real_open(path
, flags
, mode
);
267 int fd
= real_open("/dev/null", O_RDWR
, 0);
269 drm_shim_fd_register(fd
, NULL
);
273 PUBLIC
int open64(const char*, int, ...) __attribute__((alias("open")));
275 /* Fakes stat to return character device stuff for our fake render node. */
276 PUBLIC
int __xstat(int ver
, const char *path
, struct stat
*st
)
280 /* Note: call real stat if we're in the process of probing for a free
283 if (render_node_minor
== -1)
284 return real___xstat(ver
, path
, st
);
286 /* Fool libdrm's probe of whether the /sys dir for this char dev is
289 char *sys_dev_drm_dir
;
290 nfasprintf(&sys_dev_drm_dir
,
291 "/sys/dev/char/%d:%d/device/drm",
292 DRM_MAJOR
, render_node_minor
);
293 if (strcmp(path
, sys_dev_drm_dir
) == 0) {
294 free(sys_dev_drm_dir
);
297 free(sys_dev_drm_dir
);
299 if (strcmp(path
, render_node_path
) != 0)
300 return real___xstat(ver
, path
, st
);
302 memset(st
, 0, sizeof(*st
));
303 st
->st_rdev
= makedev(DRM_MAJOR
, render_node_minor
);
304 st
->st_mode
= S_IFCHR
;
309 /* Fakes stat to return character device stuff for our fake render node. */
310 PUBLIC
int __xstat64(int ver
, const char *path
, struct stat64
*st
)
314 /* Note: call real stat if we're in the process of probing for a free
317 if (render_node_minor
== -1)
318 return real___xstat64(ver
, path
, st
);
320 /* Fool libdrm's probe of whether the /sys dir for this char dev is
323 char *sys_dev_drm_dir
;
324 nfasprintf(&sys_dev_drm_dir
,
325 "/sys/dev/char/%d:%d/device/drm",
326 DRM_MAJOR
, render_node_minor
);
327 if (strcmp(path
, sys_dev_drm_dir
) == 0) {
328 free(sys_dev_drm_dir
);
331 free(sys_dev_drm_dir
);
333 if (strcmp(path
, render_node_path
) != 0)
334 return real___xstat64(ver
, path
, st
);
336 memset(st
, 0, sizeof(*st
));
337 st
->st_rdev
= makedev(DRM_MAJOR
, render_node_minor
);
338 st
->st_mode
= S_IFCHR
;
343 /* Fakes fstat to return character device stuff for our fake render node. */
344 PUBLIC
int __fxstat(int ver
, int fd
, struct stat
*st
)
348 struct shim_fd
*shim_fd
= drm_shim_fd_lookup(fd
);
351 return real___fxstat(ver
, fd
, st
);
353 memset(st
, 0, sizeof(*st
));
354 st
->st_rdev
= makedev(DRM_MAJOR
, render_node_minor
);
355 st
->st_mode
= S_IFCHR
;
360 PUBLIC
int __fxstat64(int ver
, int fd
, struct stat64
*st
)
364 struct shim_fd
*shim_fd
= drm_shim_fd_lookup(fd
);
367 return real___fxstat64(ver
, fd
, st
);
369 memset(st
, 0, sizeof(*st
));
370 st
->st_rdev
= makedev(DRM_MAJOR
, render_node_minor
);
371 st
->st_mode
= S_IFCHR
;
376 /* Tracks if the opendir was on /dev/dri. */
378 opendir(const char *name
)
382 DIR *dir
= real_opendir(name
);
383 if (strcmp(name
, "/dev/dri") == 0) {
385 /* If /dev/dri didn't exist, we still want to be able to return our
386 * fake /dev/dri/render* even though we probably can't
387 * mkdir("/dev/dri"). Return a fake DIR pointer for that.
392 mtx_lock(&shim_lock
);
393 _mesa_set_add(opendir_set
, dir
);
394 mtx_unlock(&shim_lock
);
400 /* If we've reached the end of the real directory list and we're
401 * looking at /dev/dri, add our render node to the list.
403 PUBLIC
struct dirent
*
408 struct dirent
*ent
= NULL
;
410 if (dir
!= fake_dev_dri
)
411 ent
= real_readdir(dir
);
412 static struct dirent render_node_dirent
= { 0 };
415 mtx_lock(&shim_lock
);
416 if (_mesa_set_search(opendir_set
, dir
)) {
417 strcpy(render_node_dirent
.d_name
,
418 render_node_dirent_name
);
419 ent
= &render_node_dirent
;
420 _mesa_set_remove_key(opendir_set
, dir
);
422 mtx_unlock(&shim_lock
);
428 /* If we've reached the end of the real directory list and we're
429 * looking at /dev/dri, add our render node to the list.
431 PUBLIC
struct dirent64
*
436 struct dirent64
*ent
= NULL
;
437 if (dir
!= fake_dev_dri
)
438 ent
= real_readdir64(dir
);
439 static struct dirent64 render_node_dirent
= { 0 };
442 mtx_lock(&shim_lock
);
443 if (_mesa_set_search(opendir_set
, dir
)) {
444 strcpy(render_node_dirent
.d_name
,
445 render_node_dirent_name
);
446 ent
= &render_node_dirent
;
447 _mesa_set_remove_key(opendir_set
, dir
);
449 mtx_unlock(&shim_lock
);
455 /* Cleans up tracking of opendir("/dev/dri") */
461 mtx_lock(&shim_lock
);
462 _mesa_set_remove_key(opendir_set
, dir
);
463 mtx_unlock(&shim_lock
);
465 if (dir
!= fake_dev_dri
)
466 return real_closedir(dir
);
471 /* Handles libdrm's readlink to figure out what kind of device we have. */
473 readlink(const char *path
, char *buf
, size_t size
)
477 if (strcmp(path
, subsystem_path
) != 0)
478 return real_readlink(path
, buf
, size
);
480 static const struct {
484 { "/pci", DRM_BUS_PCI
},
485 { "/usb", DRM_BUS_USB
},
486 { "/platform", DRM_BUS_PLATFORM
},
487 { "/spi", DRM_BUS_PLATFORM
},
488 { "/host1x", DRM_BUS_HOST1X
},
491 for (uint32_t i
= 0; i
< ARRAY_SIZE(bus_types
); i
++) {
492 if (bus_types
[i
].bus_type
!= shim_device
.bus_type
)
495 strncpy(buf
, bus_types
[i
].name
, size
);
500 return strlen(buf
) + 1;
503 /* Handles libdrm's realpath to figure out what kind of device we have. */
505 realpath(const char *path
, char *resolved_path
)
509 if (strcmp(path
, device_path
) != 0)
510 return real_realpath(path
, resolved_path
);
512 strcpy(resolved_path
, path
);
514 return resolved_path
;
517 /* Main entrypoint to DRM drivers: the ioctl syscall. We send all ioctls on
518 * our DRM fd to drm_shim_ioctl().
521 ioctl(int fd
, unsigned long request
, ...)
526 va_start(ap
, request
);
527 void *arg
= va_arg(ap
, void *);
530 struct shim_fd
*shim_fd
= drm_shim_fd_lookup(fd
);
532 return real_ioctl(fd
, request
, arg
);
534 return drm_shim_ioctl(fd
, request
, arg
);
537 /* Gallium uses this to dup the incoming fd on gbm screen creation */
539 fcntl(int fd
, int cmd
, ...)
543 struct shim_fd
*shim_fd
= drm_shim_fd_lookup(fd
);
547 void *arg
= va_arg(ap
, void *);
550 int ret
= real_fcntl(fd
, cmd
, arg
);
552 if (shim_fd
&& (cmd
== F_DUPFD
|| cmd
== F_DUPFD_CLOEXEC
))
553 drm_shim_fd_register(ret
, shim_fd
);
557 PUBLIC
int fcntl64(int, int, ...)
558 __attribute__((alias("fcntl")));
560 /* I wrote this when trying to fix gallium screen creation, leaving it around
561 * since it's probably good to have.
568 int ret
= real_dup(fd
);
570 struct shim_fd
*shim_fd
= drm_shim_fd_lookup(fd
);
571 if (shim_fd
&& ret
>= 0)
572 drm_shim_fd_register(ret
, shim_fd
);
578 mmap(void *addr
, size_t length
, int prot
, int flags
, int fd
, off_t offset
)
582 struct shim_fd
*shim_fd
= drm_shim_fd_lookup(fd
);
584 return drm_shim_mmap(shim_fd
, length
, prot
, flags
, fd
, offset
);
586 return real_mmap(addr
, length
, prot
, flags
, fd
, offset
);
588 PUBLIC
void *mmap64(void*, size_t, int, int, int, off_t
)
589 __attribute__((alias("mmap")));