summaryrefslogtreecommitdiffstats
path: root/src/drm-shim
diff options
context:
space:
mode:
Diffstat (limited to 'src/drm-shim')
-rw-r--r--src/drm-shim/README.md34
-rw-r--r--src/drm-shim/device.c339
-rw-r--r--src/drm-shim/drm_shim.c532
-rw-r--r--src/drm-shim/drm_shim.h83
-rw-r--r--src/drm-shim/meson.build34
5 files changed, 1022 insertions, 0 deletions
diff --git a/src/drm-shim/README.md b/src/drm-shim/README.md
new file mode 100644
index 00000000000..031a4218229
--- /dev/null
+++ b/src/drm-shim/README.md
@@ -0,0 +1,34 @@
+# DRM shim - Fake GEM kernel drivers in userspace for CI
+
+On CI systems where we don't control the kernel, it would be nice to
+be able to present either no-op GEM devices (for shader-db runs) or
+simulator-backed GEM devices (for testing against a software simulator
+or FPGA). This lets us do that by intercepting libc calls and
+exposing render nodes.
+
+## Limitations
+
+- Doesn't know how to handle DRM fds getting passed over the wire from
+ X11 (Could we use kmsro to support the X11 case?).
+- libc interception is rather glibc-specific and fragile.
+- Can easily break gdb if the libc interceptor code is what's broken.
+ (ulimit -c unlimited and doing gdb on the core after the fact can
+ help)
+
+## Using
+
+You choose the backend by setting `LD_PRELOAD` to the shim you want.
+Since this will effectively fake another DRM device to your system,
+you may need some work on your userspace to get your test application
+to use it if it's not the only DRM device present. Setting
+`DRM_SHIM_DEBUG=1` in the environment will print out what path the
+shim initialized on.
+
+For piglit tests, you can set:
+
+```
+PIGLIT_PLATFORM=gbm
+WAFFLE_GBM_DEVICE=<path from DRM_SHIM_DEBUG>
+```
+
+See your drm-shim backend's README for details on how to use it.
diff --git a/src/drm-shim/device.c b/src/drm-shim/device.c
new file mode 100644
index 00000000000..fe1cbbba351
--- /dev/null
+++ b/src/drm-shim/device.c
@@ -0,0 +1,339 @@
+/*
+ * 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 core GEM support (particularly ioctls) underneath the libc ioctl
+ * wrappers, and calls into the driver-specific code as necessary.
+ */
+
+#include <c11/threads.h>
+#include <errno.h>
+#include <linux/memfd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include "drm-uapi/drm.h"
+#include "drm_shim.h"
+#include "util/hash_table.h"
+#include "util/u_atomic.h"
+
+static mtx_t handle_lock = _MTX_INITIALIZER_NP;
+
+#ifndef HAVE_MEMFD_CREATE
+#include <sys/syscall.h>
+
+static inline int
+memfd_create(const char *name, unsigned int flags)
+{
+ return syscall(SYS_memfd_create, name, flags);
+}
+#endif
+
+/* Global state for the shim shared between libc, core, and driver. */
+struct shim_device shim_device;
+
+static uint32_t
+uint_key_hash(const void *key)
+{
+ return (uintptr_t)key;
+}
+
+static bool
+uint_key_compare(const void *a, const void *b)
+{
+ return a == b;
+}
+
+/**
+ * Called when the first libc shim is called, to initialize GEM simulation
+ * state (other than the shims themselves).
+ */
+void
+drm_shim_device_init(void)
+{
+ shim_device.fd_map = _mesa_hash_table_create(NULL,
+ uint_key_hash,
+ uint_key_compare);
+
+ drm_shim_driver_init();
+}
+
+static struct shim_fd *
+drm_shim_file_create(int fd)
+{
+ struct shim_fd *shim_fd = calloc(1, sizeof(*shim_fd));
+
+ shim_fd->fd = fd;
+ shim_fd->handles = _mesa_hash_table_create(NULL,
+ uint_key_hash,
+ uint_key_compare);
+
+ return shim_fd;
+}
+
+/**
+ * Called when the libc shims have interposed an open or dup of our simulated
+ * DRM device.
+ */
+void drm_shim_fd_register(int fd, struct shim_fd *shim_fd)
+{
+ if (!shim_fd)
+ shim_fd = drm_shim_file_create(fd);
+
+ _mesa_hash_table_insert(shim_device.fd_map, (void *)(uintptr_t)(fd + 1), shim_fd);
+}
+
+struct shim_fd *
+drm_shim_fd_lookup(int fd)
+{
+ if (fd == -1)
+ return NULL;
+
+ struct hash_entry *entry =
+ _mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1));
+
+ if (!entry)
+ return NULL;
+ return entry->data;
+}
+
+/* ioctl used by drmGetVersion() */
+static int
+drm_shim_ioctl_version(int fd, unsigned long request, void *arg)
+{
+ struct drm_version *args = arg;
+ const char *date = "20190320";
+ const char *desc = "shim";
+
+ if (args->name)
+ strncpy(args->name, shim_device.driver_name, args->name_len);
+ if (args->date)
+ strncpy(args->date, date, args->date_len);
+ if (args->desc)
+ strncpy(args->desc, desc, args->desc_len);
+ args->name_len = strlen(shim_device.driver_name);
+ args->date_len = strlen(date);
+ args->desc_len = strlen(desc);
+
+ return 0;
+}
+
+static int
+drm_shim_ioctl_get_cap(int fd, unsigned long request, void *arg)
+{
+ struct drm_get_cap *gc = arg;
+
+ switch (gc->capability) {
+ case DRM_CAP_PRIME:
+ case DRM_CAP_SYNCOBJ:
+ gc->value = 1;
+ return 0;
+
+ default:
+ fprintf(stderr, "DRM_IOCTL_GET_CAP: unhandled 0x%x\n",
+ (int)gc->capability);
+ return -1;
+ }
+}
+
+static int
+drm_shim_ioctl_gem_close(int fd, unsigned long request, void *arg)
+{
+ struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
+ struct drm_gem_close *c = arg;
+
+ if (!c->handle)
+ return 0;
+
+ mtx_lock(&handle_lock);
+ struct hash_entry *entry =
+ _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)c->handle);
+ if (!entry) {
+ mtx_unlock(&handle_lock);
+ return -EINVAL;
+ }
+
+ struct shim_bo *bo = entry->data;
+ _mesa_hash_table_remove(shim_fd->handles, entry);
+ drm_shim_bo_put(bo);
+ mtx_unlock(&handle_lock);
+ return 0;
+}
+
+static int
+drm_shim_ioctl_stub(int fd, unsigned long request, void *arg)
+{
+ return 0;
+}
+
+ioctl_fn_t core_ioctls[] = {
+ [_IOC_NR(DRM_IOCTL_VERSION)] = drm_shim_ioctl_version,
+ [_IOC_NR(DRM_IOCTL_GET_CAP)] = drm_shim_ioctl_get_cap,
+ [_IOC_NR(DRM_IOCTL_GEM_CLOSE)] = drm_shim_ioctl_gem_close,
+ [_IOC_NR(DRM_IOCTL_SYNCOBJ_CREATE)] = drm_shim_ioctl_stub,
+ [_IOC_NR(DRM_IOCTL_SYNCOBJ_DESTROY)] = drm_shim_ioctl_stub,
+ [_IOC_NR(DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD)] = drm_shim_ioctl_stub,
+ [_IOC_NR(DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE)] = drm_shim_ioctl_stub,
+};
+
+/**
+ * Implements the GEM core ioctls, and calls into driver-specific ioctls.
+ */
+int
+drm_shim_ioctl(int fd, unsigned long request, void *arg)
+{
+ int type = _IOC_TYPE(request);
+ int nr = _IOC_NR(request);
+
+ assert(type == DRM_IOCTL_BASE);
+
+ if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
+ int driver_nr = nr - DRM_COMMAND_BASE;
+
+ if (driver_nr < shim_device.driver_ioctl_count &&
+ shim_device.driver_ioctls[driver_nr]) {
+ return shim_device.driver_ioctls[driver_nr](fd, request, arg);
+ }
+ } else {
+ if (nr < ARRAY_SIZE(core_ioctls) && core_ioctls[nr]) {
+ return core_ioctls[nr](fd, request, arg);
+ }
+ }
+
+ if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
+ fprintf(stderr,
+ "DRM_SHIM: unhandled driver DRM ioctl %d (0x%08lx)\n",
+ nr - DRM_COMMAND_BASE, request);
+ } else {
+ fprintf(stderr,
+ "DRM_SHIM: unhandled core DRM ioctl 0x%X (0x%08lx)\n",
+ nr, request);
+ }
+
+ abort();
+}
+
+void
+drm_shim_bo_init(struct shim_bo *bo, size_t size)
+{
+ bo->size = size;
+ bo->fd = memfd_create("shim bo", MFD_CLOEXEC);
+ if (bo->fd == -1) {
+ fprintf(stderr, "Failed to create BO: %s\n", strerror(errno));
+ abort();
+ }
+
+ if (ftruncate(bo->fd, size) == -1) {
+ fprintf(stderr, "Failed to size BO: %s\n", strerror(errno));
+ abort();
+ }
+}
+
+struct shim_bo *
+drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle)
+{
+ if (!handle)
+ return NULL;
+
+ mtx_lock(&handle_lock);
+ struct hash_entry *entry =
+ _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)handle);
+ struct shim_bo *bo = entry ? entry->data : NULL;
+ mtx_unlock(&handle_lock);
+
+ if (bo)
+ p_atomic_inc(&bo->refcount);
+
+ return bo;
+}
+
+void
+drm_shim_bo_get(struct shim_bo *bo)
+{
+ p_atomic_inc(&bo->refcount);
+}
+
+void
+drm_shim_bo_put(struct shim_bo *bo)
+{
+ if (p_atomic_dec_return(&bo->refcount) == 0)
+ return;
+
+ if (shim_device.driver_bo_free)
+ shim_device.driver_bo_free(bo);
+ close(bo->fd);
+ free(bo);
+}
+
+int
+drm_shim_bo_get_handle(struct shim_fd *shim_fd, struct shim_bo *bo)
+{
+ /* We should probably have some real datastructure for finding the free
+ * number.
+ */
+ mtx_lock(&handle_lock);
+ for (int new_handle = 1; ; new_handle++) {
+ void *key = (void *)(uintptr_t)new_handle;
+ if (!_mesa_hash_table_search(shim_fd->handles, key)) {
+ drm_shim_bo_get(bo);
+ _mesa_hash_table_insert(shim_fd->handles, key, bo);
+ mtx_unlock(&handle_lock);
+ return new_handle;
+ }
+ }
+ mtx_unlock(&handle_lock);
+
+ return 0;
+}
+
+/* Creates an mmap offset for the BO in the DRM fd.
+ *
+ * XXX: We should be maintaining a u_mm allocator where the mmap offsets
+ * allocate the size of the BO and it can be used to look the BO back up.
+ * Instead, we just stuff the shim's pointer as the return value, and treat
+ * the incoming mmap offset on the DRM fd as a BO pointer. This doesn't work
+ * if someone tries to map a subset of the BO, but it's enough to get V3D
+ * working for now.
+ */
+uint64_t
+drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd, struct shim_bo *bo)
+{
+ return (uintptr_t)bo;
+}
+
+/* For mmap() on the DRM fd, look up the BO from the "offset" and map the BO's
+ * fd.
+ */
+void *
+drm_shim_mmap(struct shim_fd *shim_fd, size_t length, int prot, int flags,
+ int fd, off_t offset)
+{
+ struct shim_bo *bo = (void *)(uintptr_t)offset;
+
+ return mmap(NULL, length, prot, flags, bo->fd, 0);
+}
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")));
diff --git a/src/drm-shim/drm_shim.h b/src/drm-shim/drm_shim.h
new file mode 100644
index 00000000000..b14353a7962
--- /dev/null
+++ b/src/drm-shim/drm_shim.h
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+#include "util/macros.h"
+#include "util/hash_table.h"
+
+#ifdef __linux__
+#define DRM_MAJOR 226
+#endif
+
+typedef int (*ioctl_fn_t)(int fd, unsigned long request, void *arg);
+
+struct shim_bo;
+
+struct shim_device {
+ /* Mapping from int fd to struct shim_fd *. */
+ struct hash_table *fd_map;
+
+ int (**driver_ioctls)(int fd, unsigned long request, void *arg);
+ int driver_ioctl_count;
+
+ void (*driver_bo_free)(struct shim_bo *bo);
+
+ /* Returned by drmGetVersion(). */
+ const char *driver_name;
+};
+
+extern struct shim_device shim_device;
+
+struct shim_fd {
+ int fd;
+ /* mapping from int gem handle to struct shim_bo *. */
+ struct hash_table *handles;
+};
+
+struct shim_bo {
+ int fd;
+ void *map;
+ int refcount;
+ uint32_t size;
+};
+
+/* Core support. */
+extern int render_node_minor;
+void drm_shim_device_init(void);
+void drm_shim_override_file(const char *contents,
+ const char *path_format, ...) PRINTFLIKE(2, 3);
+void drm_shim_fd_register(int fd, struct shim_fd *shim_fd);
+struct shim_fd *drm_shim_fd_lookup(int fd);
+int drm_shim_ioctl(int fd, unsigned long request, void *arg);
+void *drm_shim_mmap(struct shim_fd *shim_fd, size_t length, int prot, int flags,
+ int fd, off_t offset);
+
+void drm_shim_bo_init(struct shim_bo *bo, size_t size);
+void drm_shim_bo_get(struct shim_bo *bo);
+void drm_shim_bo_put(struct shim_bo *bo);
+struct shim_bo *drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle);
+int drm_shim_bo_get_handle(struct shim_fd *shim_fd, struct shim_bo *bo);
+uint64_t drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd,
+ struct shim_bo *bo);
+
+/* driver-specific hooks. */
+void drm_shim_driver_init(void);
diff --git a/src/drm-shim/meson.build b/src/drm-shim/meson.build
new file mode 100644
index 00000000000..1638c5ae728
--- /dev/null
+++ b/src/drm-shim/meson.build
@@ -0,0 +1,34 @@
+# Copyright © 2019 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 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.
+
+drm_shim = static_library(
+ ['drm_shim'],
+ [
+ 'device.c',
+ 'drm_shim.c',
+ ],
+ link_with: libmesa_util,
+ include_directories: [inc_common],
+ dependencies: [dep_dl],
+ c_args : [c_vis_args, '-std=gnu99'],
+)
+dep_drm_shim = declare_dependency(
+ link_with: drm_shim
+)