Path: blob/21.2-virgl/src/broadcom/vulkan/v3dv_bo.c
4560 views
/*1* Copyright © 2019 Raspberry Pi2*3* Permission is hereby granted, free of charge, to any person obtaining a4* copy of this software and associated documentation files (the "Software"),5* to deal in the Software without restriction, including without limitation6* the rights to use, copy, modify, merge, publish, distribute, sublicense,7* and/or sell copies of the Software, and to permit persons to whom the8* Software is furnished to do so, subject to the following conditions:9*10* The above copyright notice and this permission notice (including the next11* paragraph) shall be included in all copies or substantial portions of the12* Software.13*14* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR15* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,16* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL17* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER18* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING19* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS20* IN THE SOFTWARE.21*/2223#include "v3dv_private.h"2425#include <errno.h>26#include <sys/mman.h>2728#include "drm-uapi/v3d_drm.h"29#include "util/u_memory.h"3031/* Default max size of the bo cache, in MB.32*33* FIXME: we got this value when testing some apps using the rpi4 with 4GB,34* but it should depend on the total amount of RAM. But for that we would need35* to test on real hw with different amount of RAM. Using this value for now.36*/37#define DEFAULT_MAX_BO_CACHE_SIZE 5123839/* Discarded to use a V3D_DEBUG for this, as it would mean adding a run-time40* check for most of the calls41*/42static const bool dump_stats = false;4344static void45bo_dump_stats(struct v3dv_device *device)46{47struct v3dv_bo_cache *cache = &device->bo_cache;4849fprintf(stderr, " BOs allocated: %d\n", device->bo_count);50fprintf(stderr, " BOs size: %dkb\n", device->bo_size / 1024);51fprintf(stderr, " BOs cached: %d\n", cache->cache_count);52fprintf(stderr, " BOs cached size: %dkb\n", cache->cache_size / 1024);5354if (!list_is_empty(&cache->time_list)) {55struct v3dv_bo *first = list_first_entry(&cache->time_list,56struct v3dv_bo,57time_list);58struct v3dv_bo *last = list_last_entry(&cache->time_list,59struct v3dv_bo,60time_list);6162fprintf(stderr, " oldest cache time: %ld\n",63(long)first->free_time);64fprintf(stderr, " newest cache time: %ld\n",65(long)last->free_time);6667struct timespec time;68clock_gettime(CLOCK_MONOTONIC, &time);69fprintf(stderr, " now: %ld\n",70time.tv_sec);71}7273if (cache->size_list_size) {74uint32_t empty_size_list = 0;75for (uint32_t i = 0; i < cache->size_list_size; i++) {76if (list_is_empty(&cache->size_list[i]))77empty_size_list++;78}79fprintf(stderr, " Empty size_list lists: %d\n", empty_size_list);80}81}8283static void84bo_remove_from_cache(struct v3dv_bo_cache *cache, struct v3dv_bo *bo)85{86list_del(&bo->time_list);87list_del(&bo->size_list);8889cache->cache_count--;90cache->cache_size -= bo->size;91}9293static struct v3dv_bo *94bo_from_cache(struct v3dv_device *device, uint32_t size, const char *name)95{96struct v3dv_bo_cache *cache = &device->bo_cache;97uint32_t page_index = size / 4096 - 1;9899if (cache->size_list_size <= page_index)100return NULL;101102struct v3dv_bo *bo = NULL;103104mtx_lock(&cache->lock);105if (!list_is_empty(&cache->size_list[page_index])) {106bo = list_first_entry(&cache->size_list[page_index],107struct v3dv_bo, size_list);108109/* Check that the BO has gone idle. If not, then we want to110* allocate something new instead, since we assume that the111* user will proceed to CPU map it and fill it with stuff.112*/113if (!v3dv_bo_wait(device, bo, 0)) {114mtx_unlock(&cache->lock);115return NULL;116}117118bo_remove_from_cache(cache, bo);119120bo->name = name;121}122mtx_unlock(&cache->lock);123return bo;124}125126static bool127bo_free(struct v3dv_device *device,128struct v3dv_bo *bo)129{130if (!bo)131return true;132133if (bo->map)134v3dv_bo_unmap(device, bo);135136struct drm_gem_close c;137memset(&c, 0, sizeof(c));138c.handle = bo->handle;139int ret = v3dv_ioctl(device->pdevice->render_fd, DRM_IOCTL_GEM_CLOSE, &c);140if (ret != 0)141fprintf(stderr, "close object %d: %s\n", bo->handle, strerror(errno));142143device->bo_count--;144device->bo_size -= bo->size;145146if (dump_stats) {147fprintf(stderr, "Freed %s%s%dkb:\n",148bo->name ? bo->name : "",149bo->name ? " " : "",150bo->size / 1024);151bo_dump_stats(device);152}153154vk_free(&device->vk.alloc, bo);155156return ret == 0;157}158159static void160bo_cache_free_all(struct v3dv_device *device,161bool with_lock)162{163struct v3dv_bo_cache *cache = &device->bo_cache;164165if (with_lock)166mtx_lock(&cache->lock);167list_for_each_entry_safe(struct v3dv_bo, bo, &cache->time_list,168time_list) {169bo_remove_from_cache(cache, bo);170bo_free(device, bo);171}172if (with_lock)173mtx_unlock(&cache->lock);174175}176177void178v3dv_bo_init(struct v3dv_bo *bo,179uint32_t handle,180uint32_t size,181uint32_t offset,182const char *name,183bool private)184{185bo->handle = handle;186bo->handle_bit = 1ull << (handle % 64);187bo->size = size;188bo->offset = offset;189bo->map = NULL;190bo->map_size = 0;191bo->name = name;192bo->private = private;193bo->dumb_handle = -1;194list_inithead(&bo->list_link);195}196197struct v3dv_bo *198v3dv_bo_alloc(struct v3dv_device *device,199uint32_t size,200const char *name,201bool private)202{203struct v3dv_bo *bo;204205const uint32_t page_align = 4096; /* Always allocate full pages */206size = align(size, page_align);207208if (private) {209bo = bo_from_cache(device, size, name);210if (bo) {211if (dump_stats) {212fprintf(stderr, "Allocated %s %dkb from cache:\n",213name, size / 1024);214bo_dump_stats(device);215}216return bo;217}218}219220bo = vk_alloc(&device->vk.alloc, sizeof(struct v3dv_bo), 8,221VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);222223if (!bo) {224fprintf(stderr, "Failed to allocate host memory for BO\n");225return NULL;226}227228retry:229;230231bool cleared_and_retried = false;232struct drm_v3d_create_bo create = {233.size = size234};235236int ret = v3dv_ioctl(device->pdevice->render_fd,237DRM_IOCTL_V3D_CREATE_BO, &create);238if (ret != 0) {239if (!list_is_empty(&device->bo_cache.time_list) &&240!cleared_and_retried) {241cleared_and_retried = true;242bo_cache_free_all(device, true);243goto retry;244}245246vk_free(&device->vk.alloc, bo);247fprintf(stderr, "Failed to allocate device memory for BO\n");248return NULL;249}250251assert(create.offset % page_align == 0);252assert((create.offset & 0xffffffff) == create.offset);253254v3dv_bo_init(bo, create.handle, size, create.offset, name, private);255256device->bo_count++;257device->bo_size += bo->size;258if (dump_stats) {259fprintf(stderr, "Allocated %s %dkb:\n", name, size / 1024);260bo_dump_stats(device);261}262263return bo;264}265266bool267v3dv_bo_map_unsynchronized(struct v3dv_device *device,268struct v3dv_bo *bo,269uint32_t size)270{271assert(bo != NULL && size <= bo->size);272273if (bo->map)274return bo->map;275276struct drm_v3d_mmap_bo map;277memset(&map, 0, sizeof(map));278map.handle = bo->handle;279int ret = v3dv_ioctl(device->pdevice->render_fd,280DRM_IOCTL_V3D_MMAP_BO, &map);281if (ret != 0) {282fprintf(stderr, "map ioctl failure\n");283return false;284}285286bo->map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,287device->pdevice->render_fd, map.offset);288if (bo->map == MAP_FAILED) {289fprintf(stderr, "mmap of bo %d (offset 0x%016llx, size %d) failed\n",290bo->handle, (long long)map.offset, (uint32_t)bo->size);291return false;292}293VG(VALGRIND_MALLOCLIKE_BLOCK(bo->map, bo->size, 0, false));294295bo->map_size = size;296297return true;298}299300bool301v3dv_bo_wait(struct v3dv_device *device,302struct v3dv_bo *bo,303uint64_t timeout_ns)304{305struct drm_v3d_wait_bo wait = {306.handle = bo->handle,307.timeout_ns = timeout_ns,308};309return v3dv_ioctl(device->pdevice->render_fd,310DRM_IOCTL_V3D_WAIT_BO, &wait) == 0;311}312313bool314v3dv_bo_map(struct v3dv_device *device, struct v3dv_bo *bo, uint32_t size)315{316assert(bo && size <= bo->size);317318bool ok = v3dv_bo_map_unsynchronized(device, bo, size);319if (!ok)320return false;321322ok = v3dv_bo_wait(device, bo, PIPE_TIMEOUT_INFINITE);323if (!ok) {324fprintf(stderr, "memory wait for map failed\n");325return false;326}327328return true;329}330331void332v3dv_bo_unmap(struct v3dv_device *device, struct v3dv_bo *bo)333{334assert(bo && bo->map && bo->map_size > 0);335336munmap(bo->map, bo->map_size);337VG(VALGRIND_FREELIKE_BLOCK(bo->map, 0));338bo->map = NULL;339bo->map_size = 0;340}341342static boolean343reallocate_size_list(struct v3dv_bo_cache *cache,344struct v3dv_device *device,345uint32_t size)346{347struct list_head *new_list =348vk_alloc(&device->vk.alloc, sizeof(struct list_head) * size, 8,349VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);350351if (!new_list) {352fprintf(stderr, "Failed to allocate host memory for cache bo list\n");353return false;354}355struct list_head *old_list = cache->size_list;356357/* Move old list contents over (since the array has moved, and358* therefore the pointers to the list heads have to change).359*/360for (int i = 0; i < cache->size_list_size; i++) {361struct list_head *old_head = &cache->size_list[i];362if (list_is_empty(old_head)) {363list_inithead(&new_list[i]);364} else {365new_list[i].next = old_head->next;366new_list[i].prev = old_head->prev;367new_list[i].next->prev = &new_list[i];368new_list[i].prev->next = &new_list[i];369}370}371for (int i = cache->size_list_size; i < size; i++)372list_inithead(&new_list[i]);373374cache->size_list = new_list;375cache->size_list_size = size;376vk_free(&device->vk.alloc, old_list);377378return true;379}380381void382v3dv_bo_cache_init(struct v3dv_device *device)383{384device->bo_size = 0;385device->bo_count = 0;386list_inithead(&device->bo_cache.time_list);387/* FIXME: perhaps set a initial size for the size-list, to avoid run-time388* reallocations389*/390device->bo_cache.size_list_size = 0;391392const char *max_cache_size_str = getenv("V3DV_MAX_BO_CACHE_SIZE");393if (max_cache_size_str == NULL)394device->bo_cache.max_cache_size = DEFAULT_MAX_BO_CACHE_SIZE;395else396device->bo_cache.max_cache_size = atoll(max_cache_size_str);397398if (dump_stats) {399fprintf(stderr, "MAX BO CACHE SIZE: %iMB\n", device->bo_cache.max_cache_size);400}401402device->bo_cache.max_cache_size *= 1024 * 1024;403device->bo_cache.cache_count = 0;404device->bo_cache.cache_size = 0;405}406407void408v3dv_bo_cache_destroy(struct v3dv_device *device)409{410bo_cache_free_all(device, true);411vk_free(&device->vk.alloc, device->bo_cache.size_list);412413if (dump_stats) {414fprintf(stderr, "BO stats after screen destroy:\n");415bo_dump_stats(device);416}417}418419420static void421free_stale_bos(struct v3dv_device *device,422time_t time)423{424struct v3dv_bo_cache *cache = &device->bo_cache;425bool freed_any = false;426427list_for_each_entry_safe(struct v3dv_bo, bo, &cache->time_list,428time_list) {429/* If it's more than a second old, free it. */430if (time - bo->free_time > 2) {431if (dump_stats && !freed_any) {432fprintf(stderr, "Freeing stale BOs:\n");433bo_dump_stats(device);434freed_any = true;435}436437bo_remove_from_cache(cache, bo);438bo_free(device, bo);439} else {440break;441}442}443444if (dump_stats && freed_any) {445fprintf(stderr, "Freed stale BOs:\n");446bo_dump_stats(device);447}448}449450bool451v3dv_bo_free(struct v3dv_device *device,452struct v3dv_bo *bo)453{454if (!bo)455return true;456457struct timespec time;458struct v3dv_bo_cache *cache = &device->bo_cache;459uint32_t page_index = bo->size / 4096 - 1;460461if (bo->private &&462bo->size > cache->max_cache_size - cache->cache_size) {463clock_gettime(CLOCK_MONOTONIC, &time);464mtx_lock(&cache->lock);465free_stale_bos(device, time.tv_sec);466mtx_unlock(&cache->lock);467}468469if (!bo->private ||470bo->size > cache->max_cache_size - cache->cache_size) {471return bo_free(device, bo);472}473474clock_gettime(CLOCK_MONOTONIC, &time);475mtx_lock(&cache->lock);476477if (cache->size_list_size <= page_index) {478if (!reallocate_size_list(cache, device, page_index + 1)) {479bool outcome = bo_free(device, bo);480/* If the reallocation failed, it usually means that we are out of481* memory, so we also free all the bo cache. We need to call it to482* not use the cache lock, as we are already under it.483*/484bo_cache_free_all(device, false);485mtx_unlock(&cache->lock);486return outcome;487}488}489490bo->free_time = time.tv_sec;491list_addtail(&bo->size_list, &cache->size_list[page_index]);492list_addtail(&bo->time_list, &cache->time_list);493494cache->cache_count++;495cache->cache_size += bo->size;496497if (dump_stats) {498fprintf(stderr, "Freed %s %dkb to cache:\n",499bo->name, bo->size / 1024);500bo_dump_stats(device);501}502bo->name = NULL;503504free_stale_bos(device, time.tv_sec);505506mtx_unlock(&cache->lock);507508return true;509}510511512