/* * 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 #include #include #include #include #include #include #include #include #include #include "drm-uapi/drm.h" #include "drm_shim.h" #include "util/hash_table.h" #include "util/u_atomic.h" #define SHIM_MEM_SIZE (4ull * 1024 * 1024 * 1024) #ifndef HAVE_MEMFD_CREATE #include 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); mtx_init(&shim_device.mem_lock, mtx_plain); shim_device.mem_fd = memfd_create("shim mem", MFD_CLOEXEC); assert(shim_device.mem_fd != -1); ASSERTED int ret = ftruncate(shim_device.mem_fd, SHIM_MEM_SIZE); assert(ret == 0); util_vma_heap_init(&shim_device.mem_heap, 4096, SHIM_MEM_SIZE - 4096); 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; mtx_init(&shim_fd->handle_lock, mtx_plain); 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"; args->version_major = shim_device.version_major; args->version_minor = shim_device.version_minor; args->version_patchlevel = shim_device.version_patchlevel; 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(&shim_fd->handle_lock); struct hash_entry *entry = _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)c->handle); if (!entry) { mtx_unlock(&shim_fd->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(&shim_fd->handle_lock); return 0; } static int drm_shim_ioctl_syncobj_create(int fd, unsigned long request, void *arg) { struct drm_syncobj_create *create = arg; create->handle = 1; /* 0 is invalid */ 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_syncobj_create, [_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, [_IOC_NR(DRM_IOCTL_SYNCOBJ_WAIT)] = 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) { ASSERTED 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); } return -EINVAL; } void drm_shim_bo_init(struct shim_bo *bo, size_t size) { mtx_lock(&shim_device.mem_lock); bo->mem_addr = util_vma_heap_alloc(&shim_device.mem_heap, size, 4096); mtx_unlock(&shim_device.mem_lock); assert(bo->mem_addr); bo->size = size; } struct shim_bo * drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle) { if (!handle) return NULL; mtx_lock(&shim_fd->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(&shim_fd->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); mtx_lock(&shim_device.mem_lock); util_vma_heap_free(&shim_device.mem_heap, bo->mem_addr, bo->size); mtx_unlock(&shim_device.mem_lock); 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(&shim_fd->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(&shim_fd->handle_lock); return new_handle; } } mtx_unlock(&shim_fd->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, shim_device.mem_fd, bo->mem_addr); }