device.c revision 7ec681f3
1/* 2 * Copyright © 2018 Broadcom 3 * 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: 10 * 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 13 * Software. 14 * 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. 22 */ 23 24/** @file 25 * 26 * Implements core GEM support (particularly ioctls) underneath the libc ioctl 27 * wrappers, and calls into the driver-specific code as necessary. 28 */ 29 30#include <c11/threads.h> 31#include <errno.h> 32#include <linux/memfd.h> 33#include <stdbool.h> 34#include <stdio.h> 35#include <stdlib.h> 36#include <string.h> 37#include <sys/ioctl.h> 38#include <sys/mman.h> 39#include <unistd.h> 40#include "drm-uapi/drm.h" 41#include "drm_shim.h" 42#include "util/hash_table.h" 43#include "util/u_atomic.h" 44 45#define SHIM_MEM_SIZE (4ull * 1024 * 1024 * 1024) 46 47#ifndef HAVE_MEMFD_CREATE 48#include <sys/syscall.h> 49 50static inline int 51memfd_create(const char *name, unsigned int flags) 52{ 53 return syscall(SYS_memfd_create, name, flags); 54} 55#endif 56 57/* Global state for the shim shared between libc, core, and driver. */ 58struct shim_device shim_device; 59 60long shim_page_size; 61 62static uint32_t 63uint_key_hash(const void *key) 64{ 65 return (uintptr_t)key; 66} 67 68static bool 69uint_key_compare(const void *a, const void *b) 70{ 71 return a == b; 72} 73 74/** 75 * Called when the first libc shim is called, to initialize GEM simulation 76 * state (other than the shims themselves). 77 */ 78void 79drm_shim_device_init(void) 80{ 81 shim_device.fd_map = _mesa_hash_table_create(NULL, 82 uint_key_hash, 83 uint_key_compare); 84 85 mtx_init(&shim_device.mem_lock, mtx_plain); 86 87 shim_device.mem_fd = memfd_create("shim mem", MFD_CLOEXEC); 88 assert(shim_device.mem_fd != -1); 89 90 ASSERTED int ret = ftruncate(shim_device.mem_fd, SHIM_MEM_SIZE); 91 assert(ret == 0); 92 93 /* The man page for mmap() says 94 * 95 * offset must be a multiple of the page size as returned by 96 * sysconf(_SC_PAGE_SIZE). 97 * 98 * Depending on the configuration of the kernel, this may not be 4096. Get 99 * this page size once and use it as the page size throughout, ensuring that 100 * are offsets are page-size aligned as required. Otherwise, mmap will fail 101 * with EINVAL. 102 */ 103 104 shim_page_size = sysconf(_SC_PAGE_SIZE); 105 106 util_vma_heap_init(&shim_device.mem_heap, shim_page_size, 107 SHIM_MEM_SIZE - shim_page_size); 108 109 drm_shim_driver_init(); 110} 111 112static struct shim_fd * 113drm_shim_file_create(int fd) 114{ 115 struct shim_fd *shim_fd = calloc(1, sizeof(*shim_fd)); 116 117 shim_fd->fd = fd; 118 mtx_init(&shim_fd->handle_lock, mtx_plain); 119 shim_fd->handles = _mesa_hash_table_create(NULL, 120 uint_key_hash, 121 uint_key_compare); 122 123 return shim_fd; 124} 125 126/** 127 * Called when the libc shims have interposed an open or dup of our simulated 128 * DRM device. 129 */ 130void drm_shim_fd_register(int fd, struct shim_fd *shim_fd) 131{ 132 if (!shim_fd) 133 shim_fd = drm_shim_file_create(fd); 134 135 _mesa_hash_table_insert(shim_device.fd_map, (void *)(uintptr_t)(fd + 1), shim_fd); 136} 137 138struct shim_fd * 139drm_shim_fd_lookup(int fd) 140{ 141 if (fd == -1) 142 return NULL; 143 144 struct hash_entry *entry = 145 _mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1)); 146 147 if (!entry) 148 return NULL; 149 return entry->data; 150} 151 152/* ioctl used by drmGetVersion() */ 153static int 154drm_shim_ioctl_version(int fd, unsigned long request, void *arg) 155{ 156 struct drm_version *args = arg; 157 const char *date = "20190320"; 158 const char *desc = "shim"; 159 160 args->version_major = shim_device.version_major; 161 args->version_minor = shim_device.version_minor; 162 args->version_patchlevel = shim_device.version_patchlevel; 163 164 if (args->name) 165 strncpy(args->name, shim_device.driver_name, args->name_len); 166 if (args->date) 167 strncpy(args->date, date, args->date_len); 168 if (args->desc) 169 strncpy(args->desc, desc, args->desc_len); 170 args->name_len = strlen(shim_device.driver_name); 171 args->date_len = strlen(date); 172 args->desc_len = strlen(desc); 173 174 return 0; 175} 176 177static int 178drm_shim_ioctl_get_cap(int fd, unsigned long request, void *arg) 179{ 180 struct drm_get_cap *gc = arg; 181 182 switch (gc->capability) { 183 case DRM_CAP_PRIME: 184 case DRM_CAP_SYNCOBJ: 185 case DRM_CAP_SYNCOBJ_TIMELINE: 186 gc->value = 1; 187 return 0; 188 189 default: 190 fprintf(stderr, "DRM_IOCTL_GET_CAP: unhandled 0x%x\n", 191 (int)gc->capability); 192 return -1; 193 } 194} 195 196static int 197drm_shim_ioctl_gem_close(int fd, unsigned long request, void *arg) 198{ 199 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); 200 struct drm_gem_close *c = arg; 201 202 if (!c->handle) 203 return 0; 204 205 mtx_lock(&shim_fd->handle_lock); 206 struct hash_entry *entry = 207 _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)c->handle); 208 if (!entry) { 209 mtx_unlock(&shim_fd->handle_lock); 210 return -EINVAL; 211 } 212 213 struct shim_bo *bo = entry->data; 214 _mesa_hash_table_remove(shim_fd->handles, entry); 215 drm_shim_bo_put(bo); 216 mtx_unlock(&shim_fd->handle_lock); 217 return 0; 218} 219 220static int 221drm_shim_ioctl_syncobj_create(int fd, unsigned long request, void *arg) 222{ 223 struct drm_syncobj_create *create = arg; 224 225 create->handle = 1; /* 0 is invalid */ 226 227 return 0; 228} 229 230static int 231drm_shim_ioctl_stub(int fd, unsigned long request, void *arg) 232{ 233 return 0; 234} 235 236ioctl_fn_t core_ioctls[] = { 237 [_IOC_NR(DRM_IOCTL_VERSION)] = drm_shim_ioctl_version, 238 [_IOC_NR(DRM_IOCTL_GET_CAP)] = drm_shim_ioctl_get_cap, 239 [_IOC_NR(DRM_IOCTL_GEM_CLOSE)] = drm_shim_ioctl_gem_close, 240 [_IOC_NR(DRM_IOCTL_SYNCOBJ_CREATE)] = drm_shim_ioctl_syncobj_create, 241 [_IOC_NR(DRM_IOCTL_SYNCOBJ_DESTROY)] = drm_shim_ioctl_stub, 242 [_IOC_NR(DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD)] = drm_shim_ioctl_stub, 243 [_IOC_NR(DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE)] = drm_shim_ioctl_stub, 244 [_IOC_NR(DRM_IOCTL_SYNCOBJ_WAIT)] = drm_shim_ioctl_stub, 245}; 246 247/** 248 * Implements the GEM core ioctls, and calls into driver-specific ioctls. 249 */ 250int 251drm_shim_ioctl(int fd, unsigned long request, void *arg) 252{ 253 ASSERTED int type = _IOC_TYPE(request); 254 int nr = _IOC_NR(request); 255 256 assert(type == DRM_IOCTL_BASE); 257 258 if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) { 259 int driver_nr = nr - DRM_COMMAND_BASE; 260 261 if (driver_nr < shim_device.driver_ioctl_count && 262 shim_device.driver_ioctls[driver_nr]) { 263 return shim_device.driver_ioctls[driver_nr](fd, request, arg); 264 } 265 } else { 266 if (nr < ARRAY_SIZE(core_ioctls) && core_ioctls[nr]) { 267 return core_ioctls[nr](fd, request, arg); 268 } 269 } 270 271 if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) { 272 fprintf(stderr, 273 "DRM_SHIM: unhandled driver DRM ioctl %d (0x%08lx)\n", 274 nr - DRM_COMMAND_BASE, request); 275 } else { 276 fprintf(stderr, 277 "DRM_SHIM: unhandled core DRM ioctl 0x%X (0x%08lx)\n", 278 nr, request); 279 } 280 281 return -EINVAL; 282} 283 284void 285drm_shim_bo_init(struct shim_bo *bo, size_t size) 286{ 287 288 mtx_lock(&shim_device.mem_lock); 289 bo->mem_addr = util_vma_heap_alloc(&shim_device.mem_heap, size, shim_page_size); 290 mtx_unlock(&shim_device.mem_lock); 291 assert(bo->mem_addr); 292 293 bo->size = size; 294} 295 296struct shim_bo * 297drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle) 298{ 299 if (!handle) 300 return NULL; 301 302 mtx_lock(&shim_fd->handle_lock); 303 struct hash_entry *entry = 304 _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)handle); 305 struct shim_bo *bo = entry ? entry->data : NULL; 306 mtx_unlock(&shim_fd->handle_lock); 307 308 if (bo) 309 p_atomic_inc(&bo->refcount); 310 311 return bo; 312} 313 314void 315drm_shim_bo_get(struct shim_bo *bo) 316{ 317 p_atomic_inc(&bo->refcount); 318} 319 320void 321drm_shim_bo_put(struct shim_bo *bo) 322{ 323 if (p_atomic_dec_return(&bo->refcount) == 0) 324 return; 325 326 if (shim_device.driver_bo_free) 327 shim_device.driver_bo_free(bo); 328 329 mtx_lock(&shim_device.mem_lock); 330 util_vma_heap_free(&shim_device.mem_heap, bo->mem_addr, bo->size); 331 mtx_unlock(&shim_device.mem_lock); 332 free(bo); 333} 334 335int 336drm_shim_bo_get_handle(struct shim_fd *shim_fd, struct shim_bo *bo) 337{ 338 /* We should probably have some real datastructure for finding the free 339 * number. 340 */ 341 mtx_lock(&shim_fd->handle_lock); 342 for (int new_handle = 1; ; new_handle++) { 343 void *key = (void *)(uintptr_t)new_handle; 344 if (!_mesa_hash_table_search(shim_fd->handles, key)) { 345 drm_shim_bo_get(bo); 346 _mesa_hash_table_insert(shim_fd->handles, key, bo); 347 mtx_unlock(&shim_fd->handle_lock); 348 return new_handle; 349 } 350 } 351 mtx_unlock(&shim_fd->handle_lock); 352 353 return 0; 354} 355 356/* Creates an mmap offset for the BO in the DRM fd. 357 * 358 * XXX: We should be maintaining a u_mm allocator where the mmap offsets 359 * allocate the size of the BO and it can be used to look the BO back up. 360 * Instead, we just stuff the shim's pointer as the return value, and treat 361 * the incoming mmap offset on the DRM fd as a BO pointer. This doesn't work 362 * if someone tries to map a subset of the BO, but it's enough to get V3D 363 * working for now. 364 */ 365uint64_t 366drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd, struct shim_bo *bo) 367{ 368 return (uintptr_t)bo; 369} 370 371/* For mmap() on the DRM fd, look up the BO from the "offset" and map the BO's 372 * fd. 373 */ 374void * 375drm_shim_mmap(struct shim_fd *shim_fd, size_t length, int prot, int flags, 376 int fd, off_t offset) 377{ 378 struct shim_bo *bo = (void *)(uintptr_t)offset; 379 380 /* The offset we pass to mmap must be aligned to the page size */ 381 assert((bo->mem_addr & (shim_page_size - 1)) == 0); 382 383 return mmap(NULL, length, prot, flags, shim_device.mem_fd, bo->mem_addr); 384} 385