1 /*
2 * Copyright 2021 Alyssa Rosenzweig
3 * Copyright 2019 Collabora, Ltd.
4 * SPDX-License-Identifier: MIT
5 */
6
7 #include "agx_bo.h"
8 #include <inttypes.h>
9 #include <stdlib.h>
10 #include "util/hash_table.h"
11 #include "util/ralloc.h"
12 #include "agx_device.h"
13 #include "decode.h"
14
15 /* Helper to calculate the bucket index of a BO */
16 static unsigned
agx_bucket_index(size_t size)17 agx_bucket_index(size_t size)
18 {
19 /* Round down to POT to compute a bucket index */
20 unsigned bucket_index = util_logbase2(size);
21
22 /* Clamp to supported buckets. Huge allocations use the largest bucket */
23 bucket_index = CLAMP(bucket_index, MIN_BO_CACHE_BUCKET, MAX_BO_CACHE_BUCKET);
24
25 /* Reindex from 0 */
26 return (bucket_index - MIN_BO_CACHE_BUCKET);
27 }
28
29 static struct list_head *
agx_bucket(struct agx_device * dev,size_t size)30 agx_bucket(struct agx_device *dev, size_t size)
31 {
32 return &dev->bo_cache.buckets[agx_bucket_index(size)];
33 }
34
35 static void
agx_bo_cache_remove_locked(struct agx_device * dev,struct agx_bo * bo)36 agx_bo_cache_remove_locked(struct agx_device *dev, struct agx_bo *bo)
37 {
38 simple_mtx_assert_locked(&dev->bo_cache.lock);
39 list_del(&bo->bucket_link);
40 list_del(&bo->lru_link);
41 dev->bo_cache.size -= bo->size;
42 }
43
44 /* Tries to fetch a BO of sufficient size with the appropriate flags from the
45 * BO cache. If it succeeds, it returns that BO and removes the BO from the
46 * cache. If it fails, it returns NULL signaling the caller to allocate a new
47 * BO. */
48
49 struct agx_bo *
agx_bo_cache_fetch(struct agx_device * dev,size_t size,size_t align,uint32_t flags,const bool dontwait)50 agx_bo_cache_fetch(struct agx_device *dev, size_t size, size_t align,
51 uint32_t flags, const bool dontwait)
52 {
53 simple_mtx_lock(&dev->bo_cache.lock);
54 struct list_head *bucket = agx_bucket(dev, size);
55 struct agx_bo *bo = NULL;
56
57 /* Iterate the bucket looking for something suitable */
58 list_for_each_entry_safe(struct agx_bo, entry, bucket, bucket_link) {
59 if (entry->size < size || entry->flags != flags)
60 continue;
61
62 /* Do not return more than 2x oversized BOs. */
63 if (entry->size > 2 * size)
64 continue;
65
66 if (align > entry->align)
67 continue;
68
69 /* This one works, use it */
70 agx_bo_cache_remove_locked(dev, entry);
71 bo = entry;
72 break;
73 }
74 simple_mtx_unlock(&dev->bo_cache.lock);
75
76 return bo;
77 }
78
79 static void
agx_bo_cache_evict_stale_bos(struct agx_device * dev,unsigned tv_sec)80 agx_bo_cache_evict_stale_bos(struct agx_device *dev, unsigned tv_sec)
81 {
82 struct timespec time;
83
84 clock_gettime(CLOCK_MONOTONIC, &time);
85 list_for_each_entry_safe(struct agx_bo, entry, &dev->bo_cache.lru,
86 lru_link) {
87 /* We want all entries that have been used more than 1 sec ago to be
88 * dropped, others can be kept. Note the <= 2 check and not <= 1. It's
89 * here to account for the fact that we're only testing ->tv_sec, not
90 * ->tv_nsec. That means we might keep entries that are between 1 and 2
91 * seconds old, but we don't really care, as long as unused BOs are
92 * dropped at some point.
93 */
94 if (time.tv_sec - entry->last_used <= 2)
95 break;
96
97 agx_bo_cache_remove_locked(dev, entry);
98 agx_bo_free(dev, entry);
99 }
100 }
101
102 static void
agx_bo_cache_put_locked(struct agx_device * dev,struct agx_bo * bo)103 agx_bo_cache_put_locked(struct agx_device *dev, struct agx_bo *bo)
104 {
105 struct list_head *bucket = agx_bucket(dev, bo->size);
106 struct timespec time;
107
108 /* Add us to the bucket */
109 list_addtail(&bo->bucket_link, bucket);
110
111 /* Add us to the LRU list and update the last_used field. */
112 list_addtail(&bo->lru_link, &dev->bo_cache.lru);
113 clock_gettime(CLOCK_MONOTONIC, &time);
114 bo->last_used = time.tv_sec;
115
116 /* Update statistics */
117 dev->bo_cache.size += bo->size;
118
119 if (0) {
120 printf("BO cache: %zu KiB (+%zu KiB from %s, hit/miss %" PRIu64
121 "/%" PRIu64 ")\n",
122 DIV_ROUND_UP(dev->bo_cache.size, 1024),
123 DIV_ROUND_UP(bo->size, 1024), bo->label,
124 p_atomic_read(&dev->bo_cache.hits),
125 p_atomic_read(&dev->bo_cache.misses));
126 }
127
128 /* Update label for debug */
129 bo->label = "Unused (BO cache)";
130
131 /* Let's do some cleanup in the BO cache while we hold the lock. */
132 agx_bo_cache_evict_stale_bos(dev, time.tv_sec);
133 }
134
135 /* Tries to add a BO to the cache. Returns if it was successful */
136 static bool
agx_bo_cache_put(struct agx_device * dev,struct agx_bo * bo)137 agx_bo_cache_put(struct agx_device *dev, struct agx_bo *bo)
138 {
139 if (bo->flags & AGX_BO_SHARED) {
140 return false;
141 } else {
142 simple_mtx_lock(&dev->bo_cache.lock);
143 agx_bo_cache_put_locked(dev, bo);
144 simple_mtx_unlock(&dev->bo_cache.lock);
145
146 return true;
147 }
148 }
149
150 void
agx_bo_cache_evict_all(struct agx_device * dev)151 agx_bo_cache_evict_all(struct agx_device *dev)
152 {
153 simple_mtx_lock(&dev->bo_cache.lock);
154 for (unsigned i = 0; i < ARRAY_SIZE(dev->bo_cache.buckets); ++i) {
155 struct list_head *bucket = &dev->bo_cache.buckets[i];
156
157 list_for_each_entry_safe(struct agx_bo, entry, bucket, bucket_link) {
158 agx_bo_cache_remove_locked(dev, entry);
159 agx_bo_free(dev, entry);
160 }
161 }
162 simple_mtx_unlock(&dev->bo_cache.lock);
163 }
164
165 void
agx_bo_reference(struct agx_bo * bo)166 agx_bo_reference(struct agx_bo *bo)
167 {
168 if (bo) {
169 ASSERTED int count = p_atomic_inc_return(&bo->refcnt);
170 assert(count != 1);
171 }
172 }
173
174 struct label_stat {
175 const char *label;
176 uint32_t count;
177 size_t alloc_B;
178 size_t mapped_B;
179 };
180
181 static void
account_bo(struct label_stat * stat,struct agx_bo * bo)182 account_bo(struct label_stat *stat, struct agx_bo *bo)
183 {
184 stat->count++;
185 stat->alloc_B += bo->size;
186
187 if (bo->_map != NULL)
188 stat->mapped_B += bo->size;
189 }
190
191 static void
print_size(FILE * fp,size_t size_B)192 print_size(FILE *fp, size_t size_B)
193 {
194 if (size_B >= (1024 * 1024 * 1024)) {
195 fprintf(fp, "%.1f GiB", (double)size_B / (double)(1024 * 1024 * 1024));
196 } else if (size_B >= (1024 * 1024)) {
197 fprintf(fp, "%.1f MiB", (double)size_B / (double)(1024 * 1024));
198 } else if (size_B >= 1024) {
199 fprintf(fp, "%zu KiB", DIV_ROUND_UP(size_B, 1024));
200 } else {
201 fprintf(fp, "%zu B", size_B);
202 }
203 }
204
205 static void
print_stat(FILE * fp,struct label_stat * stat)206 print_stat(FILE *fp, struct label_stat *stat)
207 {
208 const char *BOLD = "\e[1m";
209 const char *RESET = "\e[0m";
210
211 fprintf(fp, "%s%s%s: ", BOLD, stat->label, RESET);
212 print_size(fp, stat->alloc_B);
213
214 if (stat->mapped_B) {
215 fprintf(fp, ", mapped ");
216 print_size(fp, stat->mapped_B);
217 }
218
219 fprintf(fp, ", %u BOs\n", stat->count);
220 }
221
222 static int
compare_size(const void * a_,const void * b_)223 compare_size(const void *a_, const void *b_)
224 {
225 const struct label_stat *const *label_a = a_;
226 const struct label_stat *const *label_b = b_;
227
228 size_t a = (*label_a)->alloc_B;
229 size_t b = (*label_b)->alloc_B;
230
231 return (a > b) ? 1 : (a < b) ? -1 : 0;
232 }
233
234 static void
agx_bo_dump_all(struct agx_device * dev)235 agx_bo_dump_all(struct agx_device *dev)
236 {
237 struct label_stat accum = {.label = "Total"};
238 struct hash_table *totals = _mesa_string_hash_table_create(NULL);
239 bool verbose = dev->debug & AGX_DBG_BODUMPVERBOSE;
240
241 if (verbose)
242 fprintf(stderr, "---\n");
243
244 for (uint32_t handle = 0; handle < dev->max_handle; handle++) {
245 struct agx_bo *bo = agx_lookup_bo(dev, handle);
246 if (!bo->size)
247 continue;
248
249 if (verbose) {
250 fprintf(stderr, "%u: %s %zu KiB\n", handle, bo->label,
251 bo->size / 1024);
252 }
253
254 account_bo(&accum, bo);
255
256 assert(bo->label != NULL && "Everything must be labeled");
257
258 struct hash_entry *ent = _mesa_hash_table_search(totals, bo->label);
259 struct label_stat *ls;
260 if (ent != NULL) {
261 ls = ent->data;
262 } else {
263 ls = rzalloc(totals, struct label_stat);
264 ls->label = bo->label;
265 _mesa_hash_table_insert(totals, bo->label, ls);
266 }
267
268 account_bo(ls, bo);
269 }
270
271 if (verbose) {
272 fprintf(stderr, "\n");
273 }
274
275 unsigned nr_labels = _mesa_hash_table_num_entries(totals);
276 struct label_stat **stats =
277 rzalloc_array(totals, struct label_stat *, nr_labels);
278 unsigned i = 0;
279
280 hash_table_foreach(totals, ent) {
281 assert(i < nr_labels);
282 stats[i++] = ent->data;
283 }
284
285 assert(i == nr_labels);
286
287 /* Sort labels in ascending order of size */
288 qsort(stats, nr_labels, sizeof(struct label_stat *), compare_size);
289
290 for (i = 0; i < nr_labels; ++i) {
291 print_stat(stderr, stats[i]);
292 }
293
294 print_stat(stderr, &accum);
295
296 if (verbose)
297 fprintf(stderr, "---\n\n");
298 else
299 fprintf(stderr, "\n");
300
301 ralloc_free(totals);
302 }
303
304 static void
agx_bo_dump_all_periodic(struct agx_device * dev)305 agx_bo_dump_all_periodic(struct agx_device *dev)
306 {
307 if (likely(!(dev->debug & (AGX_DBG_BODUMP | AGX_DBG_BODUMPVERBOSE))))
308 return;
309
310 static time_t agx_last_dumped_time = 0;
311
312 time_t now = time(NULL);
313 if (now == agx_last_dumped_time)
314 return;
315
316 agx_bo_dump_all(dev);
317 agx_last_dumped_time = now;
318 }
319
320 void
agx_bo_unreference(struct agx_device * dev,struct agx_bo * bo)321 agx_bo_unreference(struct agx_device *dev, struct agx_bo *bo)
322 {
323 if (!bo)
324 return;
325
326 /* Don't return to cache if there are still references */
327 if (p_atomic_dec_return(&bo->refcnt))
328 return;
329
330 pthread_mutex_lock(&dev->bo_map_lock);
331
332 /* Someone might have imported this BO while we were waiting for the
333 * lock, let's make sure it's still not referenced before freeing it.
334 */
335 if (p_atomic_read(&bo->refcnt) == 0) {
336 assert(!p_atomic_read_relaxed(&bo->writer));
337
338 if (dev->debug & AGX_DBG_TRACE)
339 agxdecode_track_free(dev->agxdecode, bo);
340
341 if (!agx_bo_cache_put(dev, bo))
342 agx_bo_free(dev, bo);
343 }
344
345 agx_bo_dump_all_periodic(dev);
346
347 pthread_mutex_unlock(&dev->bo_map_lock);
348 }
349
350 struct agx_bo *
agx_bo_create(struct agx_device * dev,size_t size,unsigned align,enum agx_bo_flags flags,const char * label)351 agx_bo_create(struct agx_device *dev, size_t size, unsigned align,
352 enum agx_bo_flags flags, const char *label)
353 {
354 struct agx_bo *bo;
355 assert(size > 0);
356
357 /* BOs are allocated in pages */
358 size = ALIGN_POT(size, (size_t)dev->params.vm_page_size);
359 align = MAX2(align, dev->params.vm_page_size);
360
361 /* See if we have a BO already in the cache */
362 bo = agx_bo_cache_fetch(dev, size, align, flags, true);
363
364 /* Update stats based on the first attempt to fetch */
365 if (bo != NULL)
366 p_atomic_inc(&dev->bo_cache.hits);
367 else
368 p_atomic_inc(&dev->bo_cache.misses);
369
370 /* Otherwise, allocate a fresh BO. If allocation fails, we can try waiting
371 * for something in the cache. But if there's no nothing suitable, we should
372 * flush the cache to make space for the new allocation.
373 */
374 if (!bo)
375 bo = dev->ops.bo_alloc(dev, size, align, flags);
376 if (!bo)
377 bo = agx_bo_cache_fetch(dev, size, align, flags, false);
378 if (!bo) {
379 agx_bo_cache_evict_all(dev);
380 bo = dev->ops.bo_alloc(dev, size, align, flags);
381 }
382
383 if (!bo) {
384 fprintf(stderr, "BO creation failed\n");
385 return NULL;
386 }
387
388 bo->label = label;
389 p_atomic_set(&bo->refcnt, 1);
390
391 if (dev->debug & AGX_DBG_TRACE) {
392 agx_bo_map(bo);
393 agxdecode_track_alloc(dev->agxdecode, bo);
394 }
395
396 agx_bo_dump_all_periodic(dev);
397 return bo;
398 }
399