v3dv_bo.c revision 7ec681f3
1/* 2 * Copyright © 2019 Raspberry Pi 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 DEALINGS 21 * IN THE SOFTWARE. 22 */ 23 24#include "v3dv_private.h" 25 26#include <errno.h> 27#include <sys/mman.h> 28 29#include "drm-uapi/v3d_drm.h" 30#include "util/u_memory.h" 31 32/* Default max size of the bo cache, in MB. 33 * 34 * FIXME: we got this value when testing some apps using the rpi4 with 4GB, 35 * but it should depend on the total amount of RAM. But for that we would need 36 * to test on real hw with different amount of RAM. Using this value for now. 37 */ 38#define DEFAULT_MAX_BO_CACHE_SIZE 512 39 40/* Discarded to use a V3D_DEBUG for this, as it would mean adding a run-time 41 * check for most of the calls 42 */ 43static const bool dump_stats = false; 44 45static void 46bo_dump_stats(struct v3dv_device *device) 47{ 48 struct v3dv_bo_cache *cache = &device->bo_cache; 49 50 fprintf(stderr, " BOs allocated: %d\n", device->bo_count); 51 fprintf(stderr, " BOs size: %dkb\n", device->bo_size / 1024); 52 fprintf(stderr, " BOs cached: %d\n", cache->cache_count); 53 fprintf(stderr, " BOs cached size: %dkb\n", cache->cache_size / 1024); 54 55 if (!list_is_empty(&cache->time_list)) { 56 struct v3dv_bo *first = list_first_entry(&cache->time_list, 57 struct v3dv_bo, 58 time_list); 59 struct v3dv_bo *last = list_last_entry(&cache->time_list, 60 struct v3dv_bo, 61 time_list); 62 63 fprintf(stderr, " oldest cache time: %ld\n", 64 (long)first->free_time); 65 fprintf(stderr, " newest cache time: %ld\n", 66 (long)last->free_time); 67 68 struct timespec time; 69 clock_gettime(CLOCK_MONOTONIC, &time); 70 fprintf(stderr, " now: %ld\n", 71 time.tv_sec); 72 } 73 74 if (cache->size_list_size) { 75 uint32_t empty_size_list = 0; 76 for (uint32_t i = 0; i < cache->size_list_size; i++) { 77 if (list_is_empty(&cache->size_list[i])) 78 empty_size_list++; 79 } 80 fprintf(stderr, " Empty size_list lists: %d\n", empty_size_list); 81 } 82} 83 84static void 85bo_remove_from_cache(struct v3dv_bo_cache *cache, struct v3dv_bo *bo) 86{ 87 list_del(&bo->time_list); 88 list_del(&bo->size_list); 89 90 cache->cache_count--; 91 cache->cache_size -= bo->size; 92} 93 94static struct v3dv_bo * 95bo_from_cache(struct v3dv_device *device, uint32_t size, const char *name) 96{ 97 struct v3dv_bo_cache *cache = &device->bo_cache; 98 uint32_t page_index = size / 4096 - 1; 99 100 if (cache->size_list_size <= page_index) 101 return NULL; 102 103 struct v3dv_bo *bo = NULL; 104 105 mtx_lock(&cache->lock); 106 if (!list_is_empty(&cache->size_list[page_index])) { 107 bo = list_first_entry(&cache->size_list[page_index], 108 struct v3dv_bo, size_list); 109 110 /* Check that the BO has gone idle. If not, then we want to 111 * allocate something new instead, since we assume that the 112 * user will proceed to CPU map it and fill it with stuff. 113 */ 114 if (!v3dv_bo_wait(device, bo, 0)) { 115 mtx_unlock(&cache->lock); 116 return NULL; 117 } 118 119 bo_remove_from_cache(cache, bo); 120 121 bo->name = name; 122 } 123 mtx_unlock(&cache->lock); 124 return bo; 125} 126 127static bool 128bo_free(struct v3dv_device *device, 129 struct v3dv_bo *bo) 130{ 131 if (!bo) 132 return true; 133 134 if (bo->map) 135 v3dv_bo_unmap(device, bo); 136 137 struct drm_gem_close c; 138 memset(&c, 0, sizeof(c)); 139 c.handle = bo->handle; 140 int ret = v3dv_ioctl(device->pdevice->render_fd, DRM_IOCTL_GEM_CLOSE, &c); 141 if (ret != 0) 142 fprintf(stderr, "close object %d: %s\n", bo->handle, strerror(errno)); 143 144 device->bo_count--; 145 device->bo_size -= bo->size; 146 147 if (dump_stats) { 148 fprintf(stderr, "Freed %s%s%dkb:\n", 149 bo->name ? bo->name : "", 150 bo->name ? " " : "", 151 bo->size / 1024); 152 bo_dump_stats(device); 153 } 154 155 vk_free(&device->vk.alloc, bo); 156 157 return ret == 0; 158} 159 160static void 161bo_cache_free_all(struct v3dv_device *device, 162 bool with_lock) 163{ 164 struct v3dv_bo_cache *cache = &device->bo_cache; 165 166 if (with_lock) 167 mtx_lock(&cache->lock); 168 list_for_each_entry_safe(struct v3dv_bo, bo, &cache->time_list, 169 time_list) { 170 bo_remove_from_cache(cache, bo); 171 bo_free(device, bo); 172 } 173 if (with_lock) 174 mtx_unlock(&cache->lock); 175 176} 177 178void 179v3dv_bo_init(struct v3dv_bo *bo, 180 uint32_t handle, 181 uint32_t size, 182 uint32_t offset, 183 const char *name, 184 bool private) 185{ 186 bo->handle = handle; 187 bo->handle_bit = 1ull << (handle % 64); 188 bo->size = size; 189 bo->offset = offset; 190 bo->map = NULL; 191 bo->map_size = 0; 192 bo->name = name; 193 bo->private = private; 194 bo->dumb_handle = -1; 195 list_inithead(&bo->list_link); 196} 197 198struct v3dv_bo * 199v3dv_bo_alloc(struct v3dv_device *device, 200 uint32_t size, 201 const char *name, 202 bool private) 203{ 204 struct v3dv_bo *bo; 205 206 const uint32_t page_align = 4096; /* Always allocate full pages */ 207 size = align(size, page_align); 208 209 if (private) { 210 bo = bo_from_cache(device, size, name); 211 if (bo) { 212 if (dump_stats) { 213 fprintf(stderr, "Allocated %s %dkb from cache:\n", 214 name, size / 1024); 215 bo_dump_stats(device); 216 } 217 return bo; 218 } 219 } 220 221 bo = vk_alloc(&device->vk.alloc, sizeof(struct v3dv_bo), 8, 222 VK_SYSTEM_ALLOCATION_SCOPE_DEVICE); 223 224 if (!bo) { 225 fprintf(stderr, "Failed to allocate host memory for BO\n"); 226 return NULL; 227 } 228 229 retry: 230 ; 231 232 bool cleared_and_retried = false; 233 struct drm_v3d_create_bo create = { 234 .size = size 235 }; 236 237 int ret = v3dv_ioctl(device->pdevice->render_fd, 238 DRM_IOCTL_V3D_CREATE_BO, &create); 239 if (ret != 0) { 240 if (!list_is_empty(&device->bo_cache.time_list) && 241 !cleared_and_retried) { 242 cleared_and_retried = true; 243 bo_cache_free_all(device, true); 244 goto retry; 245 } 246 247 vk_free(&device->vk.alloc, bo); 248 fprintf(stderr, "Failed to allocate device memory for BO\n"); 249 return NULL; 250 } 251 252 assert(create.offset % page_align == 0); 253 assert((create.offset & 0xffffffff) == create.offset); 254 255 v3dv_bo_init(bo, create.handle, size, create.offset, name, private); 256 257 device->bo_count++; 258 device->bo_size += bo->size; 259 if (dump_stats) { 260 fprintf(stderr, "Allocated %s %dkb:\n", name, size / 1024); 261 bo_dump_stats(device); 262 } 263 264 return bo; 265} 266 267bool 268v3dv_bo_map_unsynchronized(struct v3dv_device *device, 269 struct v3dv_bo *bo, 270 uint32_t size) 271{ 272 assert(bo != NULL && size <= bo->size); 273 274 if (bo->map) 275 return bo->map; 276 277 struct drm_v3d_mmap_bo map; 278 memset(&map, 0, sizeof(map)); 279 map.handle = bo->handle; 280 int ret = v3dv_ioctl(device->pdevice->render_fd, 281 DRM_IOCTL_V3D_MMAP_BO, &map); 282 if (ret != 0) { 283 fprintf(stderr, "map ioctl failure\n"); 284 return false; 285 } 286 287 bo->map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 288 device->pdevice->render_fd, map.offset); 289 if (bo->map == MAP_FAILED) { 290 fprintf(stderr, "mmap of bo %d (offset 0x%016llx, size %d) failed\n", 291 bo->handle, (long long)map.offset, (uint32_t)bo->size); 292 return false; 293 } 294 VG(VALGRIND_MALLOCLIKE_BLOCK(bo->map, bo->size, 0, false)); 295 296 bo->map_size = size; 297 298 return true; 299} 300 301bool 302v3dv_bo_wait(struct v3dv_device *device, 303 struct v3dv_bo *bo, 304 uint64_t timeout_ns) 305{ 306 struct drm_v3d_wait_bo wait = { 307 .handle = bo->handle, 308 .timeout_ns = timeout_ns, 309 }; 310 return v3dv_ioctl(device->pdevice->render_fd, 311 DRM_IOCTL_V3D_WAIT_BO, &wait) == 0; 312} 313 314bool 315v3dv_bo_map(struct v3dv_device *device, struct v3dv_bo *bo, uint32_t size) 316{ 317 assert(bo && size <= bo->size); 318 319 bool ok = v3dv_bo_map_unsynchronized(device, bo, size); 320 if (!ok) 321 return false; 322 323 ok = v3dv_bo_wait(device, bo, PIPE_TIMEOUT_INFINITE); 324 if (!ok) { 325 fprintf(stderr, "memory wait for map failed\n"); 326 return false; 327 } 328 329 return true; 330} 331 332void 333v3dv_bo_unmap(struct v3dv_device *device, struct v3dv_bo *bo) 334{ 335 assert(bo && bo->map && bo->map_size > 0); 336 337 munmap(bo->map, bo->map_size); 338 VG(VALGRIND_FREELIKE_BLOCK(bo->map, 0)); 339 bo->map = NULL; 340 bo->map_size = 0; 341} 342 343static boolean 344reallocate_size_list(struct v3dv_bo_cache *cache, 345 struct v3dv_device *device, 346 uint32_t size) 347{ 348 struct list_head *new_list = 349 vk_alloc(&device->vk.alloc, sizeof(struct list_head) * size, 8, 350 VK_SYSTEM_ALLOCATION_SCOPE_DEVICE); 351 352 if (!new_list) { 353 fprintf(stderr, "Failed to allocate host memory for cache bo list\n"); 354 return false; 355 } 356 struct list_head *old_list = cache->size_list; 357 358 /* Move old list contents over (since the array has moved, and 359 * therefore the pointers to the list heads have to change). 360 */ 361 for (int i = 0; i < cache->size_list_size; i++) { 362 struct list_head *old_head = &cache->size_list[i]; 363 if (list_is_empty(old_head)) { 364 list_inithead(&new_list[i]); 365 } else { 366 new_list[i].next = old_head->next; 367 new_list[i].prev = old_head->prev; 368 new_list[i].next->prev = &new_list[i]; 369 new_list[i].prev->next = &new_list[i]; 370 } 371 } 372 for (int i = cache->size_list_size; i < size; i++) 373 list_inithead(&new_list[i]); 374 375 cache->size_list = new_list; 376 cache->size_list_size = size; 377 vk_free(&device->vk.alloc, old_list); 378 379 return true; 380} 381 382void 383v3dv_bo_cache_init(struct v3dv_device *device) 384{ 385 device->bo_size = 0; 386 device->bo_count = 0; 387 list_inithead(&device->bo_cache.time_list); 388 /* FIXME: perhaps set a initial size for the size-list, to avoid run-time 389 * reallocations 390 */ 391 device->bo_cache.size_list_size = 0; 392 393 const char *max_cache_size_str = getenv("V3DV_MAX_BO_CACHE_SIZE"); 394 if (max_cache_size_str == NULL) 395 device->bo_cache.max_cache_size = DEFAULT_MAX_BO_CACHE_SIZE; 396 else 397 device->bo_cache.max_cache_size = atoll(max_cache_size_str); 398 399 if (dump_stats) { 400 fprintf(stderr, "MAX BO CACHE SIZE: %iMB\n", device->bo_cache.max_cache_size); 401 } 402 403 device->bo_cache.max_cache_size *= 1024 * 1024; 404 device->bo_cache.cache_count = 0; 405 device->bo_cache.cache_size = 0; 406} 407 408void 409v3dv_bo_cache_destroy(struct v3dv_device *device) 410{ 411 bo_cache_free_all(device, true); 412 vk_free(&device->vk.alloc, device->bo_cache.size_list); 413 414 if (dump_stats) { 415 fprintf(stderr, "BO stats after screen destroy:\n"); 416 bo_dump_stats(device); 417 } 418} 419 420 421static void 422free_stale_bos(struct v3dv_device *device, 423 time_t time) 424{ 425 struct v3dv_bo_cache *cache = &device->bo_cache; 426 bool freed_any = false; 427 428 list_for_each_entry_safe(struct v3dv_bo, bo, &cache->time_list, 429 time_list) { 430 /* If it's more than a second old, free it. */ 431 if (time - bo->free_time > 2) { 432 if (dump_stats && !freed_any) { 433 fprintf(stderr, "Freeing stale BOs:\n"); 434 bo_dump_stats(device); 435 freed_any = true; 436 } 437 438 bo_remove_from_cache(cache, bo); 439 bo_free(device, bo); 440 } else { 441 break; 442 } 443 } 444 445 if (dump_stats && freed_any) { 446 fprintf(stderr, "Freed stale BOs:\n"); 447 bo_dump_stats(device); 448 } 449} 450 451bool 452v3dv_bo_free(struct v3dv_device *device, 453 struct v3dv_bo *bo) 454{ 455 if (!bo) 456 return true; 457 458 struct timespec time; 459 struct v3dv_bo_cache *cache = &device->bo_cache; 460 uint32_t page_index = bo->size / 4096 - 1; 461 462 if (bo->private && 463 bo->size > cache->max_cache_size - cache->cache_size) { 464 clock_gettime(CLOCK_MONOTONIC, &time); 465 mtx_lock(&cache->lock); 466 free_stale_bos(device, time.tv_sec); 467 mtx_unlock(&cache->lock); 468 } 469 470 if (!bo->private || 471 bo->size > cache->max_cache_size - cache->cache_size) { 472 return bo_free(device, bo); 473 } 474 475 clock_gettime(CLOCK_MONOTONIC, &time); 476 mtx_lock(&cache->lock); 477 478 if (cache->size_list_size <= page_index) { 479 if (!reallocate_size_list(cache, device, page_index + 1)) { 480 bool outcome = bo_free(device, bo); 481 /* If the reallocation failed, it usually means that we are out of 482 * memory, so we also free all the bo cache. We need to call it to 483 * not use the cache lock, as we are already under it. 484 */ 485 bo_cache_free_all(device, false); 486 mtx_unlock(&cache->lock); 487 return outcome; 488 } 489 } 490 491 bo->free_time = time.tv_sec; 492 list_addtail(&bo->size_list, &cache->size_list[page_index]); 493 list_addtail(&bo->time_list, &cache->time_list); 494 495 cache->cache_count++; 496 cache->cache_size += bo->size; 497 498 if (dump_stats) { 499 fprintf(stderr, "Freed %s %dkb to cache:\n", 500 bo->name, bo->size / 1024); 501 bo_dump_stats(device); 502 } 503 bo->name = NULL; 504 505 free_stale_bos(device, time.tv_sec); 506 507 mtx_unlock(&cache->lock); 508 509 return true; 510} 511