1/* 2 * Copyright (C) 2016 Etnaviv Project 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 FROM, 20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 * SOFTWARE. 22 * 23 * Authors: 24 * Christian Gmeiner <christian.gmeiner@gmail.com> 25 */ 26 27#include "etnaviv_priv.h" 28#include "etnaviv_drmif.h" 29 30void _etna_bo_del(struct etna_bo *bo); 31 32static void add_bucket(struct etna_bo_cache *cache, int size) 33{ 34 unsigned i = cache->num_buckets; 35 36 assert(i < ARRAY_SIZE(cache->cache_bucket)); 37 38 list_inithead(&cache->cache_bucket[i].list); 39 cache->cache_bucket[i].size = size; 40 cache->num_buckets++; 41} 42 43void etna_bo_cache_init(struct etna_bo_cache *cache) 44{ 45 unsigned long size, cache_max_size = 64 * 1024 * 1024; 46 47 /* OK, so power of two buckets was too wasteful of memory. 48 * Give 3 other sizes between each power of two, to hopefully 49 * cover things accurately enough. (The alternative is 50 * probably to just go for exact matching of sizes, and assume 51 * that for things like composited window resize the tiled 52 * width/height alignment and rounding of sizes to pages will 53 * get us useful cache hit rates anyway) 54 */ 55 add_bucket(cache, 4096); 56 add_bucket(cache, 4096 * 2); 57 add_bucket(cache, 4096 * 3); 58 59 /* Initialize the linked lists for BO reuse cache. */ 60 for (size = 4 * 4096; size <= cache_max_size; size *= 2) { 61 add_bucket(cache, size); 62 add_bucket(cache, size + size * 1 / 4); 63 add_bucket(cache, size + size * 2 / 4); 64 add_bucket(cache, size + size * 3 / 4); 65 } 66} 67 68/* Frees older cached buffers. Called under etna_drm_table_lock */ 69void etna_bo_cache_cleanup(struct etna_bo_cache *cache, time_t time) 70{ 71 unsigned i; 72 73 if (cache->time == time) 74 return; 75 76 for (i = 0; i < cache->num_buckets; i++) { 77 struct etna_bo_bucket *bucket = &cache->cache_bucket[i]; 78 struct etna_bo *bo; 79 80 while (!list_is_empty(&bucket->list)) { 81 bo = LIST_ENTRY(struct etna_bo, bucket->list.next, list); 82 83 /* keep things in cache for at least 1 second: */ 84 if (time && ((time - bo->free_time) <= 1)) 85 break; 86 87 VG_BO_OBTAIN(bo); 88 list_del(&bo->list); 89 _etna_bo_del(bo); 90 } 91 } 92 93 cache->time = time; 94} 95 96static struct etna_bo_bucket *get_bucket(struct etna_bo_cache *cache, uint32_t size) 97{ 98 unsigned i; 99 100 /* hmm, this is what intel does, but I suppose we could calculate our 101 * way to the correct bucket size rather than looping.. 102 */ 103 for (i = 0; i < cache->num_buckets; i++) { 104 struct etna_bo_bucket *bucket = &cache->cache_bucket[i]; 105 if (bucket->size >= size) { 106 return bucket; 107 } 108 } 109 110 return NULL; 111} 112 113static int is_idle(struct etna_bo *bo) 114{ 115 return etna_bo_cpu_prep(bo, 116 DRM_ETNA_PREP_READ | 117 DRM_ETNA_PREP_WRITE | 118 DRM_ETNA_PREP_NOSYNC) == 0; 119} 120 121static struct etna_bo *find_in_bucket(struct etna_bo_bucket *bucket, uint32_t flags) 122{ 123 struct etna_bo *bo = NULL, *tmp; 124 125 simple_mtx_lock(&etna_drm_table_lock); 126 127 if (list_is_empty(&bucket->list)) 128 goto out_unlock; 129 130 LIST_FOR_EACH_ENTRY_SAFE(bo, tmp, &bucket->list, list) { 131 /* skip BOs with different flags */ 132 if (bo->flags != flags) 133 continue; 134 135 /* check if the first BO with matching flags is idle */ 136 if (is_idle(bo)) { 137 list_delinit(&bo->list); 138 goto out_unlock; 139 } 140 141 /* If the oldest BO is still busy, don't try younger ones */ 142 break; 143 } 144 145 /* There was no matching buffer found */ 146 bo = NULL; 147 148out_unlock: 149 simple_mtx_unlock(&etna_drm_table_lock); 150 151 return bo; 152} 153 154/* allocate a new (un-tiled) buffer object 155 * 156 * NOTE: size is potentially rounded up to bucket size 157 */ 158struct etna_bo *etna_bo_cache_alloc(struct etna_bo_cache *cache, uint32_t *size, 159 uint32_t flags) 160{ 161 struct etna_bo *bo; 162 struct etna_bo_bucket *bucket; 163 164 *size = ALIGN(*size, 4096); 165 bucket = get_bucket(cache, *size); 166 167 /* see if we can be green and recycle: */ 168 if (bucket) { 169 *size = bucket->size; 170 bo = find_in_bucket(bucket, flags); 171 if (bo) { 172 VG_BO_OBTAIN(bo); 173 p_atomic_set(&bo->refcnt, 1); 174 etna_device_ref(bo->dev); 175 return bo; 176 } 177 } 178 179 return NULL; 180} 181 182int etna_bo_cache_free(struct etna_bo_cache *cache, struct etna_bo *bo) 183{ 184 struct etna_bo_bucket *bucket; 185 186 simple_mtx_assert_locked(&etna_drm_table_lock); 187 188 bucket = get_bucket(cache, bo->size); 189 190 /* see if we can be green and recycle: */ 191 if (bucket) { 192 struct timespec time; 193 194 clock_gettime(CLOCK_MONOTONIC, &time); 195 196 bo->free_time = time.tv_sec; 197 VG_BO_RELEASE(bo); 198 list_addtail(&bo->list, &bucket->list); 199 etna_bo_cache_cleanup(cache, time.tv_sec); 200 201 /* bo's in the bucket cache don't have a ref and 202 * don't hold a ref to the dev: 203 */ 204 etna_device_del_locked(bo->dev); 205 206 return 0; 207 } 208 209 return -1; 210} 211