17e102996Smaya/*
27e102996Smaya * Copyright (C) 2012-2018 Rob Clark <robclark@freedesktop.org>
37e102996Smaya *
47e102996Smaya * Permission is hereby granted, free of charge, to any person obtaining a
57e102996Smaya * copy of this software and associated documentation files (the "Software"),
67e102996Smaya * to deal in the Software without restriction, including without limitation
77e102996Smaya * the rights to use, copy, modify, merge, publish, distribute, sublicense,
87e102996Smaya * and/or sell copies of the Software, and to permit persons to whom the
97e102996Smaya * Software is furnished to do so, subject to the following conditions:
107e102996Smaya *
117e102996Smaya * The above copyright notice and this permission notice (including the next
127e102996Smaya * paragraph) shall be included in all copies or substantial portions of the
137e102996Smaya * Software.
147e102996Smaya *
157e102996Smaya * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
167e102996Smaya * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
177e102996Smaya * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
187e102996Smaya * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
197e102996Smaya * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
207e102996Smaya * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
217e102996Smaya * SOFTWARE.
227e102996Smaya *
237e102996Smaya * Authors:
247e102996Smaya *    Rob Clark <robclark@freedesktop.org>
257e102996Smaya */
267e102996Smaya
277e102996Smaya#include "freedreno_drmif.h"
287e102996Smaya#include "freedreno_priv.h"
297e102996Smaya
307e102996Smayavoid bo_del(struct fd_bo *bo);
317ec681f3Smrgextern simple_mtx_t table_lock;
327e102996Smaya
337e102996Smayastatic void
347e102996Smayaadd_bucket(struct fd_bo_cache *cache, int size)
357e102996Smaya{
367ec681f3Smrg   unsigned int i = cache->num_buckets;
377e102996Smaya
387ec681f3Smrg   assert(i < ARRAY_SIZE(cache->cache_bucket));
397e102996Smaya
407ec681f3Smrg   list_inithead(&cache->cache_bucket[i].list);
417ec681f3Smrg   cache->cache_bucket[i].size = size;
427ec681f3Smrg   cache->num_buckets++;
437e102996Smaya}
447e102996Smaya
457e102996Smaya/**
467e102996Smaya * @coarse: if true, only power-of-two bucket sizes, otherwise
477e102996Smaya *    fill in for a bit smoother size curve..
487e102996Smaya */
497e102996Smayavoid
507e102996Smayafd_bo_cache_init(struct fd_bo_cache *cache, int coarse)
517e102996Smaya{
527ec681f3Smrg   unsigned long size, cache_max_size = 64 * 1024 * 1024;
537ec681f3Smrg
547ec681f3Smrg   /* OK, so power of two buckets was too wasteful of memory.
557ec681f3Smrg    * Give 3 other sizes between each power of two, to hopefully
567ec681f3Smrg    * cover things accurately enough.  (The alternative is
577ec681f3Smrg    * probably to just go for exact matching of sizes, and assume
587ec681f3Smrg    * that for things like composited window resize the tiled
597ec681f3Smrg    * width/height alignment and rounding of sizes to pages will
607ec681f3Smrg    * get us useful cache hit rates anyway)
617ec681f3Smrg    */
627ec681f3Smrg   add_bucket(cache, 4096);
637ec681f3Smrg   add_bucket(cache, 4096 * 2);
647ec681f3Smrg   if (!coarse)
657ec681f3Smrg      add_bucket(cache, 4096 * 3);
667ec681f3Smrg
677ec681f3Smrg   /* Initialize the linked lists for BO reuse cache. */
687ec681f3Smrg   for (size = 4 * 4096; size <= cache_max_size; size *= 2) {
697ec681f3Smrg      add_bucket(cache, size);
707ec681f3Smrg      if (!coarse) {
717ec681f3Smrg         add_bucket(cache, size + size * 1 / 4);
727ec681f3Smrg         add_bucket(cache, size + size * 2 / 4);
737ec681f3Smrg         add_bucket(cache, size + size * 3 / 4);
747ec681f3Smrg      }
757ec681f3Smrg   }
767e102996Smaya}
777e102996Smaya
787e102996Smaya/* Frees older cached buffers.  Called under table_lock */
797e102996Smayavoid
807e102996Smayafd_bo_cache_cleanup(struct fd_bo_cache *cache, time_t time)
817e102996Smaya{
827ec681f3Smrg   int i;
837e102996Smaya
847ec681f3Smrg   if (cache->time == time)
857ec681f3Smrg      return;
867e102996Smaya
877ec681f3Smrg   for (i = 0; i < cache->num_buckets; i++) {
887ec681f3Smrg      struct fd_bo_bucket *bucket = &cache->cache_bucket[i];
897ec681f3Smrg      struct fd_bo *bo;
907e102996Smaya
917ec681f3Smrg      while (!list_is_empty(&bucket->list)) {
927ec681f3Smrg         bo = LIST_ENTRY(struct fd_bo, bucket->list.next, list);
937e102996Smaya
947ec681f3Smrg         /* keep things in cache for at least 1 second: */
957ec681f3Smrg         if (time && ((time - bo->free_time) <= 1))
967ec681f3Smrg            break;
977e102996Smaya
987ec681f3Smrg         VG_BO_OBTAIN(bo);
997ec681f3Smrg         list_del(&bo->list);
1007ec681f3Smrg         bo_del(bo);
1017ec681f3Smrg      }
1027ec681f3Smrg   }
1037e102996Smaya
1047ec681f3Smrg   cache->time = time;
1057e102996Smaya}
1067e102996Smaya
1077ec681f3Smrgstatic struct fd_bo_bucket *
1087ec681f3Smrgget_bucket(struct fd_bo_cache *cache, uint32_t size)
1097e102996Smaya{
1107ec681f3Smrg   int i;
1117ec681f3Smrg
1127ec681f3Smrg   /* hmm, this is what intel does, but I suppose we could calculate our
1137ec681f3Smrg    * way to the correct bucket size rather than looping..
1147ec681f3Smrg    */
1157ec681f3Smrg   for (i = 0; i < cache->num_buckets; i++) {
1167ec681f3Smrg      struct fd_bo_bucket *bucket = &cache->cache_bucket[i];
1177ec681f3Smrg      if (bucket->size >= size) {
1187ec681f3Smrg         return bucket;
1197ec681f3Smrg      }
1207ec681f3Smrg   }
1217ec681f3Smrg
1227ec681f3Smrg   return NULL;
1237e102996Smaya}
1247e102996Smaya
1257ec681f3Smrgstatic struct fd_bo *
1267ec681f3Smrgfind_in_bucket(struct fd_bo_bucket *bucket, uint32_t flags)
1277e102996Smaya{
1287ec681f3Smrg   struct fd_bo *bo = NULL;
1297ec681f3Smrg
1307ec681f3Smrg   /* TODO .. if we had an ALLOC_FOR_RENDER flag like intel, we could
1317ec681f3Smrg    * skip the busy check.. if it is only going to be a render target
1327ec681f3Smrg    * then we probably don't need to stall..
1337ec681f3Smrg    *
1347ec681f3Smrg    * NOTE that intel takes ALLOC_FOR_RENDER bo's from the list tail
1357ec681f3Smrg    * (MRU, since likely to be in GPU cache), rather than head (LRU)..
1367ec681f3Smrg    */
1377ec681f3Smrg   simple_mtx_lock(&table_lock);
1387ec681f3Smrg   list_for_each_entry (struct fd_bo, entry, &bucket->list, list) {
1397ec681f3Smrg      if (fd_bo_state(entry) != FD_BO_STATE_IDLE)
1407ec681f3Smrg         break;
1417ec681f3Smrg      if (entry->alloc_flags == flags) {
1427ec681f3Smrg         bo = entry;
1437ec681f3Smrg         list_del(&bo->list);
1447ec681f3Smrg         break;
1457ec681f3Smrg      }
1467ec681f3Smrg   }
1477ec681f3Smrg   simple_mtx_unlock(&table_lock);
1487ec681f3Smrg
1497ec681f3Smrg   return bo;
1507e102996Smaya}
1517e102996Smaya
1527e102996Smaya/* NOTE: size is potentially rounded up to bucket size: */
1537e102996Smayastruct fd_bo *
1547e102996Smayafd_bo_cache_alloc(struct fd_bo_cache *cache, uint32_t *size, uint32_t flags)
1557e102996Smaya{
1567ec681f3Smrg   struct fd_bo *bo = NULL;
1577ec681f3Smrg   struct fd_bo_bucket *bucket;
1587e102996Smaya
1597ec681f3Smrg   *size = align(*size, 4096);
1607ec681f3Smrg   bucket = get_bucket(cache, *size);
1617e102996Smaya
1627ec681f3Smrg   /* see if we can be green and recycle: */
1637e102996Smayaretry:
1647ec681f3Smrg   if (bucket) {
1657ec681f3Smrg      *size = bucket->size;
1667ec681f3Smrg      bo = find_in_bucket(bucket, flags);
1677ec681f3Smrg      if (bo) {
1687ec681f3Smrg         VG_BO_OBTAIN(bo);
1697ec681f3Smrg         if (bo->funcs->madvise(bo, true) <= 0) {
1707ec681f3Smrg            /* we've lost the backing pages, delete and try again: */
1717ec681f3Smrg            simple_mtx_lock(&table_lock);
1727ec681f3Smrg            bo_del(bo);
1737ec681f3Smrg            simple_mtx_unlock(&table_lock);
1747ec681f3Smrg            goto retry;
1757ec681f3Smrg         }
1767ec681f3Smrg         p_atomic_set(&bo->refcnt, 1);
1777ec681f3Smrg         bo->reloc_flags = FD_RELOC_FLAGS_INIT;
1787ec681f3Smrg         return bo;
1797ec681f3Smrg      }
1807ec681f3Smrg   }
1817ec681f3Smrg
1827ec681f3Smrg   return NULL;
1837e102996Smaya}
1847e102996Smaya
1857e102996Smayaint
1867e102996Smayafd_bo_cache_free(struct fd_bo_cache *cache, struct fd_bo *bo)
1877e102996Smaya{
1887ec681f3Smrg   if (bo->nosync || bo->shared)
1897ec681f3Smrg      return -1;
1907e102996Smaya
1917ec681f3Smrg   struct fd_bo_bucket *bucket = get_bucket(cache, bo->size);
1927e102996Smaya
1937ec681f3Smrg   /* see if we can be green and recycle: */
1947ec681f3Smrg   if (bucket) {
1957ec681f3Smrg      struct timespec time;
1967e102996Smaya
1977ec681f3Smrg      bo->funcs->madvise(bo, false);
1987e102996Smaya
1997ec681f3Smrg      clock_gettime(CLOCK_MONOTONIC, &time);
2007e102996Smaya
2017ec681f3Smrg      bo->free_time = time.tv_sec;
2027ec681f3Smrg      VG_BO_RELEASE(bo);
2037ec681f3Smrg      list_addtail(&bo->list, &bucket->list);
2047ec681f3Smrg      fd_bo_cache_cleanup(cache, time.tv_sec);
2057e102996Smaya
2067ec681f3Smrg      return 0;
2077ec681f3Smrg   }
2087e102996Smaya
2097ec681f3Smrg   return -1;
2107e102996Smaya}
211