1 /*
2 * Copyright (C) 2012-2018 Rob Clark <robclark@freedesktop.org>
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 * Rob Clark <robclark@freedesktop.org>
25 */
26
27 #include "freedreno_drmif.h"
28 #include "freedreno_priv.h"
29
30 #define FD_BO_CACHE_STATS 0
31
32 #define BO_CACHE_LOG(cache, fmt, ...) do { \
33 if (FD_BO_CACHE_STATS) { \
34 mesa_logi("%s: "fmt, (cache)->name, ##__VA_ARGS__); \
35 } \
36 } while (0)
37
38 static void
bo_remove_from_bucket(struct fd_bo_bucket * bucket,struct fd_bo * bo)39 bo_remove_from_bucket(struct fd_bo_bucket *bucket, struct fd_bo *bo)
40 {
41 list_delinit(&bo->node);
42 bucket->count--;
43 }
44
45 static void
dump_cache_stats(struct fd_bo_cache * cache)46 dump_cache_stats(struct fd_bo_cache *cache)
47 {
48 if (!FD_BO_CACHE_STATS)
49 return;
50
51 static int cnt;
52
53 if ((++cnt % 32))
54 return;
55
56 int size = 0;
57 int count = 0;
58 int hits = 0;
59 int misses = 0;
60 int expired = 0;
61
62 for (int i = 0; i < cache->num_buckets; i++) {
63 char *state = "";
64
65 struct fd_bo_bucket *bucket = &cache->cache_bucket[i];
66
67 if (bucket->count > 0) {
68 struct fd_bo *bo = first_bo(&bucket->list);
69 if (fd_bo_state(bo) == FD_BO_STATE_IDLE)
70 state = " (idle)";
71 }
72
73 BO_CACHE_LOG(cache, "bucket[%u]: count=%d\thits=%d\tmisses=%d\texpired=%d%s",
74 bucket->size, bucket->count, bucket->hits,
75 bucket->misses, bucket->expired, state);
76
77 size += bucket->size * bucket->count;
78 count += bucket->count;
79 hits += bucket->hits;
80 misses += bucket->misses;
81 expired += bucket->expired;
82 }
83
84 BO_CACHE_LOG(cache, "size=%d\tcount=%d\thits=%d\tmisses=%d\texpired=%d",
85 size, count, hits, misses, expired);
86 }
87
88 static void
add_bucket(struct fd_bo_cache * cache,int size)89 add_bucket(struct fd_bo_cache *cache, int size)
90 {
91 unsigned int i = cache->num_buckets;
92 struct fd_bo_bucket *bucket = &cache->cache_bucket[i];
93
94 assert(i < ARRAY_SIZE(cache->cache_bucket));
95
96 list_inithead(&bucket->list);
97 bucket->size = size;
98 bucket->count = 0;
99 bucket->hits = 0;
100 bucket->misses = 0;
101 bucket->expired = 0;
102 cache->num_buckets++;
103 }
104
105 /**
106 * @coarse: if true, only power-of-two bucket sizes, otherwise
107 * fill in for a bit smoother size curve..
108 */
109 void
fd_bo_cache_init(struct fd_bo_cache * cache,int coarse,const char * name)110 fd_bo_cache_init(struct fd_bo_cache *cache, int coarse, const char *name)
111 {
112 unsigned long size, cache_max_size = 64 * 1024 * 1024;
113
114 cache->name = name;
115 simple_mtx_init(&cache->lock, mtx_plain);
116
117 /* OK, so power of two buckets was too wasteful of memory.
118 * Give 3 other sizes between each power of two, to hopefully
119 * cover things accurately enough. (The alternative is
120 * probably to just go for exact matching of sizes, and assume
121 * that for things like composited window resize the tiled
122 * width/height alignment and rounding of sizes to pages will
123 * get us useful cache hit rates anyway)
124 */
125 add_bucket(cache, 4096);
126 add_bucket(cache, 4096 * 2);
127 if (!coarse)
128 add_bucket(cache, 4096 * 3);
129
130 /* Initialize the linked lists for BO reuse cache. */
131 for (size = 4 * 4096; size <= cache_max_size; size *= 2) {
132 add_bucket(cache, size);
133 if (!coarse) {
134 add_bucket(cache, size + size * 1 / 4);
135 add_bucket(cache, size + size * 2 / 4);
136 add_bucket(cache, size + size * 3 / 4);
137 }
138 }
139 }
140
141 /* Frees older cached buffers. Called under table_lock */
142 void
fd_bo_cache_cleanup(struct fd_bo_cache * cache,time_t time)143 fd_bo_cache_cleanup(struct fd_bo_cache *cache, time_t time)
144 {
145 int i, cnt = 0;
146
147 if (cache->time == time)
148 return;
149
150 struct list_head freelist;
151
152 list_inithead(&freelist);
153
154 simple_mtx_lock(&cache->lock);
155 for (i = 0; i < cache->num_buckets; i++) {
156 struct fd_bo_bucket *bucket = &cache->cache_bucket[i];
157 struct fd_bo *bo;
158
159 while (!list_is_empty(&bucket->list)) {
160 bo = first_bo(&bucket->list);
161
162 /* keep things in cache for at least 1 second: */
163 if (time && ((time - bo->free_time) <= 1))
164 break;
165
166 if (cnt == 0) {
167 BO_CACHE_LOG(cache, "cache cleanup");
168 dump_cache_stats(cache);
169 }
170
171 VG_BO_OBTAIN(bo);
172 bo_remove_from_bucket(bucket, bo);
173 bucket->expired++;
174 list_addtail(&bo->node, &freelist);
175
176 cnt++;
177 }
178 }
179 simple_mtx_unlock(&cache->lock);
180
181 fd_bo_del_list_nocache(&freelist);
182
183 if (cnt > 0) {
184 BO_CACHE_LOG(cache, "cache cleaned %u BOs", cnt);
185 dump_cache_stats(cache);
186 }
187
188 cache->time = time;
189 }
190
191 static struct fd_bo_bucket *
get_bucket(struct fd_bo_cache * cache,uint32_t size)192 get_bucket(struct fd_bo_cache *cache, uint32_t size)
193 {
194 int i;
195
196 /* hmm, this is what intel does, but I suppose we could calculate our
197 * way to the correct bucket size rather than looping..
198 */
199 for (i = 0; i < cache->num_buckets; i++) {
200 struct fd_bo_bucket *bucket = &cache->cache_bucket[i];
201 if (bucket->size >= size) {
202 return bucket;
203 }
204 }
205
206 return NULL;
207 }
208
209 static struct fd_bo *
find_in_bucket(struct fd_bo_bucket * bucket,uint32_t flags)210 find_in_bucket(struct fd_bo_bucket *bucket, uint32_t flags)
211 {
212 struct fd_bo *bo = NULL;
213
214 /* TODO .. if we had an ALLOC_FOR_RENDER flag like intel, we could
215 * skip the busy check.. if it is only going to be a render target
216 * then we probably don't need to stall..
217 *
218 * NOTE that intel takes ALLOC_FOR_RENDER bo's from the list tail
219 * (MRU, since likely to be in GPU cache), rather than head (LRU)..
220 */
221 foreach_bo (entry, &bucket->list) {
222 if (fd_bo_state(entry) != FD_BO_STATE_IDLE) {
223 break;
224 }
225 if (entry->alloc_flags == flags) {
226 bo = entry;
227 bo_remove_from_bucket(bucket, bo);
228 break;
229 }
230 }
231
232 return bo;
233 }
234
235 /* NOTE: size is potentially rounded up to bucket size: */
236 struct fd_bo *
fd_bo_cache_alloc(struct fd_bo_cache * cache,uint32_t * size,uint32_t flags)237 fd_bo_cache_alloc(struct fd_bo_cache *cache, uint32_t *size, uint32_t flags)
238 {
239 struct fd_bo *bo = NULL;
240 struct fd_bo_bucket *bucket;
241
242 *size = align(*size, 4096);
243 bucket = get_bucket(cache, *size);
244
245 struct list_head freelist;
246
247 list_inithead(&freelist);
248
249 /* see if we can be green and recycle: */
250 retry:
251 if (bucket) {
252 *size = bucket->size;
253 simple_mtx_lock(&cache->lock);
254 bo = find_in_bucket(bucket, flags);
255 simple_mtx_unlock(&cache->lock);
256 if (bo) {
257 VG_BO_OBTAIN(bo);
258 if (bo->funcs->madvise(bo, true) <= 0) {
259 /* we've lost the backing pages, delete and try again: */
260 list_addtail(&bo->node, &freelist);
261 goto retry;
262 }
263 p_atomic_set(&bo->refcnt, 1);
264 bo->reloc_flags = FD_RELOC_FLAGS_INIT;
265 bucket->hits++;
266 return bo;
267 }
268 bucket->misses++;
269 }
270
271 fd_bo_del_list_nocache(&freelist);
272
273 BO_CACHE_LOG(cache, "miss on size=%u, flags=0x%x, bucket=%u", *size, flags,
274 bucket ? bucket->size : 0);
275 dump_cache_stats(cache);
276
277 return NULL;
278 }
279
280 int
fd_bo_cache_free(struct fd_bo_cache * cache,struct fd_bo * bo)281 fd_bo_cache_free(struct fd_bo_cache *cache, struct fd_bo *bo)
282 {
283 if (bo->alloc_flags & (FD_BO_SHARED | _FD_BO_NOSYNC))
284 return -1;
285
286 struct fd_bo_bucket *bucket = get_bucket(cache, bo->size);
287
288 /* see if we can be green and recycle: */
289 if (bucket) {
290 struct timespec time;
291
292 bo->funcs->madvise(bo, false);
293
294 clock_gettime(CLOCK_MONOTONIC, &time);
295
296 bo->free_time = time.tv_sec;
297 VG_BO_RELEASE(bo);
298
299 simple_mtx_lock(&cache->lock);
300 list_addtail(&bo->node, &bucket->list);
301 bucket->count++;
302 simple_mtx_unlock(&cache->lock);
303
304 fd_bo_cache_cleanup(cache, time.tv_sec);
305
306 return 0;
307 }
308
309 return -1;
310 }
311