diff options
author | Eric Anholt <[email protected]> | 2018-09-25 19:15:45 -0700 |
---|---|---|
committer | Eric Anholt <[email protected]> | 2019-07-25 08:56:19 -0700 |
commit | 82bf1979d7b7d83e8e213b11ff7787e6939c8f7e (patch) | |
tree | 595ad86905bae25e153d1c7a82041eedef0f20f7 /src/drm-shim/drm_shim.c | |
parent | c5f14322965cc006038e293902412e76ad268767 (diff) |
v3d: Introduce a DRM shim for calling out to the simulator.
The goal is to enable testing of parts of drivers without depending on any
particular kernel version or hardware being present.
Simply set LD_PRELOAD=$PREFIX/lib/libv3d_drm_shim.so in your environment,
and we'll fake a /dev/dri/renderD128 (or whatever the next available node
is) using v3dv3. That node can then be used with the surfaceless or gbm
EGL platforms.
Acked-by: Iago Toral Quiroga <[email protected]>
Diffstat (limited to 'src/drm-shim/drm_shim.c')
-rw-r--r-- | src/drm-shim/drm_shim.c | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/src/drm-shim/drm_shim.c b/src/drm-shim/drm_shim.c new file mode 100644 index 00000000000..7c7d5f0f009 --- /dev/null +++ b/src/drm-shim/drm_shim.c @@ -0,0 +1,532 @@ +/* + * Copyright © 2018 Broadcom + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/** + * @file + * + * Implements wrappers of libc functions to fake having a DRM device that + * isn't actually present in the kernel. + */ + +/* Prevent glibc from defining open64 when we want to alias it. */ +#undef _FILE_OFFSET_BITS +#define _LARGEFILE64_SOURCE + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <stdarg.h> +#include <fcntl.h> +#include <dlfcn.h> +#include <dirent.h> +#include <c11/threads.h> +#include <drm-uapi/drm.h> + +#include "util/set.h" +#include "util/u_debug.h" +#include "drm_shim.h" + +#define REAL_FUNCTION_POINTER(x) typeof(x) *real_##x + +static mtx_t shim_lock = _MTX_INITIALIZER_NP; +struct set *opendir_set; +bool drm_shim_debug; + +/* If /dev/dri doesn't exist, we'll need an arbitrary pointer that wouldn't be + * returned by any other opendir() call so we can return just our fake node. + */ +DIR *fake_dev_dri = (void *)&opendir_set; + +/* XXX: implement REAL_FUNCTION_POINTER(close); */ +REAL_FUNCTION_POINTER(closedir); +REAL_FUNCTION_POINTER(dup); +REAL_FUNCTION_POINTER(fcntl); +REAL_FUNCTION_POINTER(fopen); +REAL_FUNCTION_POINTER(ioctl); +REAL_FUNCTION_POINTER(mmap); +REAL_FUNCTION_POINTER(open); +REAL_FUNCTION_POINTER(opendir); +REAL_FUNCTION_POINTER(readdir); +REAL_FUNCTION_POINTER(readdir64); +REAL_FUNCTION_POINTER(readlink); +REAL_FUNCTION_POINTER(__xstat); +REAL_FUNCTION_POINTER(__xstat64); +REAL_FUNCTION_POINTER(__fxstat); +REAL_FUNCTION_POINTER(__fxstat64); + +/* Full path of /dev/dri/renderD* */ +static char *render_node_path; +/* renderD* */ +static char *render_node_dirent_name; +/* /sys/dev/char/major:minor/device/subsystem */ +static char *subsystem_path; +int render_node_minor = -1; + +struct file_override { + const char *path; + char *contents; +}; +static struct file_override file_overrides[10]; +static int file_overrides_count; + +/* Come up with a filename for a render node that doesn't actually exist on + * the system. + */ +static void +get_dri_render_node_minor(void) +{ + for (int i = 0; i < 10; i++) { + int minor = 128 + i; + asprintf(&render_node_dirent_name, "renderD%d", minor); + asprintf(&render_node_path, "/dev/dri/%s", + render_node_dirent_name); + struct stat st; + if (stat(render_node_path, &st) == -1) { + + render_node_minor = minor; + return; + } + } + + fprintf(stderr, "Couldn't find a spare render node slot\n"); +} + +static void *get_function_pointer(const char *name) +{ + void *func = dlsym(RTLD_NEXT, name); + if (!func) { + fprintf(stderr, "Failed to resolve %s\n", name); + abort(); + } + return func; +} + +#define GET_FUNCTION_POINTER(x) real_##x = get_function_pointer(#x) + +void +drm_shim_override_file(const char *contents, const char *path_format, ...) +{ + assert(file_overrides_count < ARRAY_SIZE(file_overrides)); + + char *path; + va_list ap; + va_start(ap, path_format); + vasprintf(&path, path_format, ap); + va_end(ap); + + struct file_override *override = &file_overrides[file_overrides_count++]; + override->path = path; + override->contents = strdup(contents); +} + +static void +destroy_shim(void) +{ + _mesa_set_destroy(opendir_set, NULL); + free(render_node_path); + free(render_node_dirent_name); + free(subsystem_path); +} + +/* Initialization, which will be called from the first general library call + * that might need to be wrapped with the shim. + */ +static void +init_shim(void) +{ + static bool inited = false; + drm_shim_debug = debug_get_bool_option("DRM_SHIM_DEBUG", false); + + /* We can't lock this, because we recurse during initialization. */ + if (inited) + return; + + /* This comes first (and we're locked), to make sure we don't recurse + * during initialization. + */ + inited = true; + + opendir_set = _mesa_set_create(NULL, + _mesa_hash_string, + _mesa_key_string_equal); + + GET_FUNCTION_POINTER(closedir); + GET_FUNCTION_POINTER(dup); + GET_FUNCTION_POINTER(fcntl); + GET_FUNCTION_POINTER(fopen); + GET_FUNCTION_POINTER(ioctl); + GET_FUNCTION_POINTER(mmap); + GET_FUNCTION_POINTER(open); + GET_FUNCTION_POINTER(opendir); + GET_FUNCTION_POINTER(readdir); + GET_FUNCTION_POINTER(readdir64); + GET_FUNCTION_POINTER(readlink); + GET_FUNCTION_POINTER(__xstat); + GET_FUNCTION_POINTER(__xstat64); + GET_FUNCTION_POINTER(__fxstat); + GET_FUNCTION_POINTER(__fxstat64); + + get_dri_render_node_minor(); + + if (drm_shim_debug) { + fprintf(stderr, "Initializing DRM shim on %s\n", + render_node_path); + } + + asprintf(&subsystem_path, + "/sys/dev/char/%d:%d/device/subsystem", + DRM_MAJOR, render_node_minor); + + drm_shim_device_init(); + + atexit(destroy_shim); +} + +/* Override libdrm's reading of various sysfs files for device enumeration. */ +PUBLIC FILE *fopen(const char *path, const char *mode) +{ + init_shim(); + + for (int i = 0; i < file_overrides_count; i++) { + if (strcmp(file_overrides[i].path, path) == 0) { + int fds[2]; + pipe(fds); + write(fds[1], file_overrides[i].contents, + strlen(file_overrides[i].contents)); + return fdopen(fds[0], "r"); + } + } + + return real_fopen(path, mode); +} +PUBLIC FILE *fopen64(const char *path, const char *mode) + __attribute__((alias("fopen"))); + +/* Intercepts open(render_node_path) to redirect it to the simulator. */ +PUBLIC int open(const char *path, int flags, ...) +{ + init_shim(); + + va_list ap; + va_start(ap, flags); + mode_t mode = va_arg(ap, mode_t); + va_end(ap); + + if (strcmp(path, render_node_path) != 0) + return real_open(path, flags, mode); + + int fd = real_open("/dev/null", O_RDWR, 0); + + drm_shim_fd_register(fd, NULL); + + return fd; +} +PUBLIC int open64(const char*, int, ...) __attribute__((alias("open"))); + +/* Fakes stat to return character device stuff for our fake render node. */ +PUBLIC int __xstat(int ver, const char *path, struct stat *st) +{ + init_shim(); + + /* Note: call real stat if we're in the process of probing for a free + * render node! + */ + if (render_node_minor == -1) + return real___xstat(ver, path, st); + + /* Fool libdrm's probe of whether the /sys dir for this char dev is + * there. + */ + char *sys_dev_drm_dir; + asprintf(&sys_dev_drm_dir, "/sys/dev/char/%d:%d/device/drm", + DRM_MAJOR, render_node_minor); + if (strcmp(path, sys_dev_drm_dir) == 0) { + free(sys_dev_drm_dir); + return 0; + } + free(sys_dev_drm_dir); + + if (strcmp(path, render_node_path) != 0) + return real___xstat(ver, path, st); + + memset(st, 0, sizeof(*st)); + st->st_rdev = makedev(DRM_MAJOR, render_node_minor); + st->st_mode = S_IFCHR; + + return 0; +} + +/* Fakes stat to return character device stuff for our fake render node. */ +PUBLIC int __xstat64(int ver, const char *path, struct stat64 *st) +{ + init_shim(); + + /* Note: call real stat if we're in the process of probing for a free + * render node! + */ + if (render_node_minor == -1) + return real___xstat64(ver, path, st); + + /* Fool libdrm's probe of whether the /sys dir for this char dev is + * there. + */ + char *sys_dev_drm_dir; + asprintf(&sys_dev_drm_dir, "/sys/dev/char/%d:%d/device/drm", + DRM_MAJOR, render_node_minor); + if (strcmp(path, sys_dev_drm_dir) == 0) { + free(sys_dev_drm_dir); + return 0; + } + free(sys_dev_drm_dir); + + if (strcmp(path, render_node_path) != 0) + return real___xstat64(ver, path, st); + + memset(st, 0, sizeof(*st)); + st->st_rdev = makedev(DRM_MAJOR, render_node_minor); + st->st_mode = S_IFCHR; + + return 0; +} + +/* Fakes fstat to return character device stuff for our fake render node. */ +PUBLIC int __fxstat(int ver, int fd, struct stat *st) +{ + init_shim(); + + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + + if (!shim_fd) + return real___fxstat(ver, fd, st); + + memset(st, 0, sizeof(*st)); + st->st_rdev = makedev(DRM_MAJOR, render_node_minor); + st->st_mode = S_IFCHR; + + return 0; +} + +PUBLIC int __fxstat64(int ver, int fd, struct stat64 *st) +{ + init_shim(); + + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + + if (!shim_fd) + return real___fxstat64(ver, fd, st); + + memset(st, 0, sizeof(*st)); + st->st_rdev = makedev(DRM_MAJOR, render_node_minor); + st->st_mode = S_IFCHR; + + return 0; +} + +/* Tracks if the opendir was on /dev/dri. */ +PUBLIC DIR * +opendir(const char *name) +{ + init_shim(); + + DIR *dir = real_opendir(name); + if (strcmp(name, "/dev/dri") == 0) { + if (!dir) { + /* If /dev/dri didn't exist, we still want to be able to return our + * fake /dev/dri/render* even though we probably can't + * mkdir("/dev/dri"). Return a fake DIR pointer for that. + */ + dir = fake_dev_dri; + } + + mtx_lock(&shim_lock); + _mesa_set_add(opendir_set, dir); + mtx_unlock(&shim_lock); + } + + return dir; +} + +/* If we've reached the end of the real directory list and we're + * looking at /dev/dri, add our render node to the list. + */ +PUBLIC struct dirent * +readdir(DIR *dir) +{ + init_shim(); + + struct dirent *ent = NULL; + + if (dir != fake_dev_dri) + ent = real_readdir(dir); + static struct dirent render_node_dirent = { 0 }; + + if (!ent) { + mtx_lock(&shim_lock); + if (_mesa_set_search(opendir_set, dir)) { + strcpy(render_node_dirent.d_name, + render_node_dirent_name); + ent = &render_node_dirent; + _mesa_set_remove_key(opendir_set, dir); + } + mtx_unlock(&shim_lock); + } + + return ent; +} + +/* If we've reached the end of the real directory list and we're + * looking at /dev/dri, add our render node to the list. + */ +PUBLIC struct dirent64 * +readdir64(DIR *dir) +{ + init_shim(); + + struct dirent64 *ent = NULL; + if (dir != fake_dev_dri) + ent = real_readdir64(dir); + static struct dirent64 render_node_dirent = { 0 }; + + if (!ent) { + mtx_lock(&shim_lock); + if (_mesa_set_search(opendir_set, dir)) { + strcpy(render_node_dirent.d_name, + render_node_dirent_name); + ent = &render_node_dirent; + _mesa_set_remove_key(opendir_set, dir); + } + mtx_unlock(&shim_lock); + } + + return ent; +} + +/* Cleans up tracking of opendir("/dev/dri") */ +PUBLIC int +closedir(DIR *dir) +{ + init_shim(); + + mtx_lock(&shim_lock); + _mesa_set_remove_key(opendir_set, dir); + mtx_unlock(&shim_lock); + + if (dir != fake_dev_dri) + return real_closedir(dir); + else + return 0; +} + +/* Handles libdrm's readlink to figure out what kind of device we have. */ +PUBLIC ssize_t +readlink(const char *path, char *buf, size_t size) +{ + init_shim(); + + if (strcmp(path, subsystem_path) != 0) + return real_readlink(path, buf, size); + strncpy(buf, "/platform", size); + buf[size - 1] = 0; + + return strlen(buf) + 1; +} + +/* Main entrypoint to DRM drivers: the ioctl syscall. We send all ioctls on + * our DRM fd to drm_shim_ioctl(). + */ +PUBLIC int +ioctl(int fd, unsigned long request, ...) +{ + init_shim(); + + va_list ap; + va_start(ap, request); + void *arg = va_arg(ap, void *); + va_end(ap); + + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + if (!shim_fd) + return real_ioctl(fd, request, arg); + + return drm_shim_ioctl(fd, request, arg); +} + +/* Gallium uses this to dup the incoming fd on gbm screen creation */ +PUBLIC int +fcntl(int fd, int cmd, ...) +{ + init_shim(); + + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + + va_list ap; + va_start(ap, cmd); + void *arg = va_arg(ap, void *); + va_end(ap); + + int ret = real_fcntl(fd, cmd, arg); + + if (shim_fd && (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC)) + drm_shim_fd_register(ret, shim_fd); + + return ret; +} +PUBLIC int fcntl64(int, int, ...) + __attribute__((alias("fcntl"))); + +/* I wrote this when trying to fix gallium screen creation, leaving it around + * since it's probably good to have. + */ +PUBLIC int +dup(int fd) +{ + init_shim(); + + int ret = real_dup(fd); + + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + if (shim_fd && ret >= 0) + drm_shim_fd_register(ret, shim_fd); + + return ret; +} + +PUBLIC void * +mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) +{ + init_shim(); + + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + if (shim_fd) + return drm_shim_mmap(shim_fd, length, prot, flags, fd, offset); + + return real_mmap(addr, length, prot, flags, fd, offset); +} +PUBLIC void *mmap64(void*, size_t, int, int, int, off_t) + __attribute__((alias("mmap"))); |