aboutsummaryrefslogtreecommitdiffstats
path: root/src/drm-shim/device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/drm-shim/device.c')
-rw-r--r--src/drm-shim/device.c339
1 files changed, 339 insertions, 0 deletions
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);
+}