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 shim_device.offset_map = _mesa_hash_table_u64_create(NULL); 86 87 mtx_init(&shim_device.mem_lock, mtx_plain); 88 89 shim_device.mem_fd = memfd_create("shim mem", MFD_CLOEXEC); 90 assert(shim_device.mem_fd != -1); 91 92 ASSERTED int ret = ftruncate(shim_device.mem_fd, SHIM_MEM_SIZE); 93 assert(ret == 0); 94 95 /* The man page for mmap() says 96 * 97 * offset must be a multiple of the page size as returned by 98 * sysconf(_SC_PAGE_SIZE). 99 * 100 * Depending on the configuration of the kernel, this may not be 4096. Get 101 * this page size once and use it as the page size throughout, ensuring that 102 * are offsets are page-size aligned as required. Otherwise, mmap will fail 103 * with EINVAL. 104 */ 105 106 shim_page_size = sysconf(_SC_PAGE_SIZE); 107 108 util_vma_heap_init(&shim_device.mem_heap, shim_page_size, 109 SHIM_MEM_SIZE - shim_page_size); 110 111 drm_shim_driver_init(); 112} 113 114static struct shim_fd * 115drm_shim_file_create(int fd) 116{ 117 struct shim_fd *shim_fd = calloc(1, sizeof(*shim_fd)); 118 119 shim_fd->fd = fd; 120 p_atomic_set(&shim_fd->refcount, 1); 121 mtx_init(&shim_fd->handle_lock, mtx_plain); 122 shim_fd->handles = _mesa_hash_table_create(NULL, 123 uint_key_hash, 124 uint_key_compare); 125 126 return shim_fd; 127} 128 129/** 130 * Called when the libc shims have interposed an open or dup of our simulated 131 * DRM device. 132 */ 133void drm_shim_fd_register(int fd, struct shim_fd *shim_fd) 134{ 135 if (!shim_fd) 136 shim_fd = drm_shim_file_create(fd); 137 else 138 p_atomic_inc(&shim_fd->refcount); 139 140 _mesa_hash_table_insert(shim_device.fd_map, (void *)(uintptr_t)(fd + 1), shim_fd); 141} 142 143static void handle_delete_fxn(struct hash_entry *entry) 144{ 145 drm_shim_bo_put(entry->data); 146} 147 148void drm_shim_fd_unregister(int fd) 149{ 150 struct hash_entry *entry = 151 _mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1)); 152 if (!entry) 153 return; 154 struct shim_fd *shim_fd = entry->data; 155 _mesa_hash_table_remove(shim_device.fd_map, entry); 156 157 if (!p_atomic_dec_zero(&shim_fd->refcount)) 158 return; 159 160 _mesa_hash_table_destroy(shim_fd->handles, handle_delete_fxn); 161 free(shim_fd); 162} 163 164struct shim_fd * 165drm_shim_fd_lookup(int fd) 166{ 167 if (fd == -1) 168 return NULL; 169 170 struct hash_entry *entry = 171 _mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1)); 172 173 if (!entry) 174 return NULL; 175 return entry->data; 176} 177 178/* ioctl used by drmGetVersion() */ 179static int 180drm_shim_ioctl_version(int fd, unsigned long request, void *arg) 181{ 182 struct drm_version *args = arg; 183 const char *date = "20190320"; 184 const char *desc = "shim"; 185 186 args->version_major = shim_device.version_major; 187 args->version_minor = shim_device.version_minor; 188 args->version_patchlevel = shim_device.version_patchlevel; 189 190 if (args->name) 191 strncpy(args->name, shim_device.driver_name, args->name_len); 192 if (args->date) 193 strncpy(args->date, date, args->date_len); 194 if (args->desc) 195 strncpy(args->desc, desc, args->desc_len); 196 args->name_len = strlen(shim_device.driver_name); 197 args->date_len = strlen(date); 198 args->desc_len = strlen(desc); 199 200 return 0; 201} 202 203static int 204drm_shim_ioctl_get_unique(int fd, unsigned long request, void *arg) 205{ 206 struct drm_unique *gu = arg; 207 208 if (gu->unique && shim_device.unique) 209 strncpy(gu->unique, shim_device.unique, gu->unique_len); 210 gu->unique_len = shim_device.unique ? strlen(shim_device.unique) : 0; 211 212 return 0; 213} 214 215static int 216drm_shim_ioctl_get_cap(int fd, unsigned long request, void *arg) 217{ 218 struct drm_get_cap *gc = arg; 219 220 switch (gc->capability) { 221 case DRM_CAP_PRIME: 222 case DRM_CAP_SYNCOBJ: 223 case DRM_CAP_SYNCOBJ_TIMELINE: 224 gc->value = 1; 225 return 0; 226 227 default: 228 fprintf(stderr, "DRM_IOCTL_GET_CAP: unhandled 0x%x\n", 229 (int)gc->capability); 230 return -1; 231 } 232} 233 234static int 235drm_shim_ioctl_gem_close(int fd, unsigned long request, void *arg) 236{ 237 struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); 238 struct drm_gem_close *c = arg; 239 240 if (!c->handle) 241 return 0; 242 243 mtx_lock(&shim_fd->handle_lock); 244 struct hash_entry *entry = 245 _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)c->handle); 246 if (!entry) { 247 mtx_unlock(&shim_fd->handle_lock); 248 return -EINVAL; 249 } 250 251 struct shim_bo *bo = entry->data; 252 _mesa_hash_table_remove(shim_fd->handles, entry); 253 drm_shim_bo_put(bo); 254 mtx_unlock(&shim_fd->handle_lock); 255 return 0; 256} 257 258static int 259drm_shim_ioctl_syncobj_create(int fd, unsigned long request, void *arg) 260{ 261 struct drm_syncobj_create *create = arg; 262 263 create->handle = 1; /* 0 is invalid */ 264 265 return 0; 266} 267 268static int 269drm_shim_ioctl_stub(int fd, unsigned long request, void *arg) 270{ 271 return 0; 272} 273 274ioctl_fn_t core_ioctls[] = { 275 [_IOC_NR(DRM_IOCTL_VERSION)] = drm_shim_ioctl_version, 276 [_IOC_NR(DRM_IOCTL_GET_UNIQUE)] = drm_shim_ioctl_get_unique, 277 [_IOC_NR(DRM_IOCTL_GET_CAP)] = drm_shim_ioctl_get_cap, 278 [_IOC_NR(DRM_IOCTL_GEM_CLOSE)] = drm_shim_ioctl_gem_close, 279 [_IOC_NR(DRM_IOCTL_SYNCOBJ_CREATE)] = drm_shim_ioctl_syncobj_create, 280 [_IOC_NR(DRM_IOCTL_SYNCOBJ_DESTROY)] = drm_shim_ioctl_stub, 281 [_IOC_NR(DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD)] = drm_shim_ioctl_stub, 282 [_IOC_NR(DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE)] = drm_shim_ioctl_stub, 283 [_IOC_NR(DRM_IOCTL_SYNCOBJ_WAIT)] = drm_shim_ioctl_stub, 284}; 285 286/** 287 * Implements the GEM core ioctls, and calls into driver-specific ioctls. 288 */ 289int 290drm_shim_ioctl(int fd, unsigned long request, void *arg) 291{ 292 ASSERTED int type = _IOC_TYPE(request); 293 int nr = _IOC_NR(request); 294 295 assert(type == DRM_IOCTL_BASE); 296 297 if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) { 298 int driver_nr = nr - DRM_COMMAND_BASE; 299 300 if (driver_nr < shim_device.driver_ioctl_count && 301 shim_device.driver_ioctls[driver_nr]) { 302 return shim_device.driver_ioctls[driver_nr](fd, request, arg); 303 } 304 } else { 305 if (nr < ARRAY_SIZE(core_ioctls) && core_ioctls[nr]) { 306 return core_ioctls[nr](fd, request, arg); 307 } 308 } 309 310 if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) { 311 fprintf(stderr, 312 "DRM_SHIM: unhandled driver DRM ioctl %d (0x%08lx)\n", 313 nr - DRM_COMMAND_BASE, request); 314 } else { 315 fprintf(stderr, 316 "DRM_SHIM: unhandled core DRM ioctl 0x%X (0x%08lx)\n", 317 nr, request); 318 } 319 320 return -EINVAL; 321} 322 323int 324drm_shim_bo_init(struct shim_bo *bo, size_t size) 325{ 326 327 mtx_lock(&shim_device.mem_lock); 328 bo->mem_addr = util_vma_heap_alloc(&shim_device.mem_heap, size, shim_page_size); 329 mtx_unlock(&shim_device.mem_lock); 330 331 if (!bo->mem_addr) 332 return -ENOMEM; 333 334 bo->size = size; 335 336 return 0; 337} 338 339struct shim_bo * 340drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle) 341{ 342 if (!handle) 343 return NULL; 344 345 mtx_lock(&shim_fd->handle_lock); 346 struct hash_entry *entry = 347 _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)handle); 348 struct shim_bo *bo = entry ? entry->data : NULL; 349 mtx_unlock(&shim_fd->handle_lock); 350 351 if (bo) 352 p_atomic_inc(&bo->refcount); 353 354 return bo; 355} 356 357void 358drm_shim_bo_get(struct shim_bo *bo) 359{ 360 p_atomic_inc(&bo->refcount); 361} 362 363void 364drm_shim_bo_put(struct shim_bo *bo) 365{ 366 if (p_atomic_dec_return(&bo->refcount) == 0) 367 return; 368 369 if (shim_device.driver_bo_free) 370 shim_device.driver_bo_free(bo); 371 372 mtx_lock(&shim_device.mem_lock); 373 util_vma_heap_free(&shim_device.mem_heap, bo->mem_addr, bo->size); 374 mtx_unlock(&shim_device.mem_lock); 375 free(bo); 376} 377 378int 379drm_shim_bo_get_handle(struct shim_fd *shim_fd, struct shim_bo *bo) 380{ 381 /* We should probably have some real datastructure for finding the free 382 * number. 383 */ 384 mtx_lock(&shim_fd->handle_lock); 385 for (int new_handle = 1; ; new_handle++) { 386 void *key = (void *)(uintptr_t)new_handle; 387 if (!_mesa_hash_table_search(shim_fd->handles, key)) { 388 drm_shim_bo_get(bo); 389 _mesa_hash_table_insert(shim_fd->handles, key, bo); 390 mtx_unlock(&shim_fd->handle_lock); 391 return new_handle; 392 } 393 } 394 mtx_unlock(&shim_fd->handle_lock); 395 396 return 0; 397} 398 399/* Creates an mmap offset for the BO in the DRM fd. 400 */ 401uint64_t 402drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd, struct shim_bo *bo) 403{ 404 mtx_lock(&shim_device.mem_lock); 405 _mesa_hash_table_u64_insert(shim_device.offset_map, bo->mem_addr, bo); 406 mtx_unlock(&shim_device.mem_lock); 407 408 /* reuse the buffer address as the mmap offset: */ 409 return bo->mem_addr; 410} 411 412/* For mmap() on the DRM fd, look up the BO from the "offset" and map the BO's 413 * fd. 414 */ 415void * 416drm_shim_mmap(struct shim_fd *shim_fd, size_t length, int prot, int flags, 417 int fd, off64_t offset) 418{ 419 mtx_lock(&shim_device.mem_lock); 420 struct shim_bo *bo = _mesa_hash_table_u64_search(shim_device.offset_map, offset); 421 mtx_unlock(&shim_device.mem_lock); 422 423 if (!bo) 424 return MAP_FAILED; 425 426 if (length > bo->size) 427 return MAP_FAILED; 428 429 /* The offset we pass to mmap must be aligned to the page size */ 430 assert((bo->mem_addr & (shim_page_size - 1)) == 0); 431 432 return mmap(NULL, length, prot, flags, shim_device.mem_fd, bo->mem_addr); 433} 434