17ec681f3Smrg/*
27ec681f3Smrg * Copyright (C) 2016 Etnaviv Project
37ec681f3Smrg *
47ec681f3Smrg * Permission is hereby granted, free of charge, to any person obtaining a
57ec681f3Smrg * copy of this software and associated documentation files (the "Software"),
67ec681f3Smrg * to deal in the Software without restriction, including without limitation
77ec681f3Smrg * the rights to use, copy, modify, merge, publish, distribute, sublicense,
87ec681f3Smrg * and/or sell copies of the Software, and to permit persons to whom the
97ec681f3Smrg * Software is furnished to do so, subject to the following conditions:
107ec681f3Smrg *
117ec681f3Smrg * The above copyright notice and this permission notice (including the next
127ec681f3Smrg * paragraph) shall be included in all copies or substantial portions of the
137ec681f3Smrg * Software.
147ec681f3Smrg *
157ec681f3Smrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
167ec681f3Smrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
177ec681f3Smrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
187ec681f3Smrg * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
197ec681f3Smrg * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
207ec681f3Smrg * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
217ec681f3Smrg * SOFTWARE.
227ec681f3Smrg *
237ec681f3Smrg * Authors:
247ec681f3Smrg *    Christian Gmeiner <christian.gmeiner@gmail.com>
257ec681f3Smrg */
267ec681f3Smrg
277ec681f3Smrg#include "etnaviv_priv.h"
287ec681f3Smrg#include "etnaviv_drmif.h"
297ec681f3Smrg
307ec681f3Smrgvoid _etna_bo_del(struct etna_bo *bo);
317ec681f3Smrg
327ec681f3Smrgstatic void add_bucket(struct etna_bo_cache *cache, int size)
337ec681f3Smrg{
347ec681f3Smrg	unsigned i = cache->num_buckets;
357ec681f3Smrg
367ec681f3Smrg	assert(i < ARRAY_SIZE(cache->cache_bucket));
377ec681f3Smrg
387ec681f3Smrg	list_inithead(&cache->cache_bucket[i].list);
397ec681f3Smrg	cache->cache_bucket[i].size = size;
407ec681f3Smrg	cache->num_buckets++;
417ec681f3Smrg}
427ec681f3Smrg
437ec681f3Smrgvoid etna_bo_cache_init(struct etna_bo_cache *cache)
447ec681f3Smrg{
457ec681f3Smrg	unsigned long size, cache_max_size = 64 * 1024 * 1024;
467ec681f3Smrg
477ec681f3Smrg	/* OK, so power of two buckets was too wasteful of memory.
487ec681f3Smrg	 * Give 3 other sizes between each power of two, to hopefully
497ec681f3Smrg	 * cover things accurately enough.  (The alternative is
507ec681f3Smrg	 * probably to just go for exact matching of sizes, and assume
517ec681f3Smrg	 * that for things like composited window resize the tiled
527ec681f3Smrg	 * width/height alignment and rounding of sizes to pages will
537ec681f3Smrg	 * get us useful cache hit rates anyway)
547ec681f3Smrg	 */
557ec681f3Smrg	add_bucket(cache, 4096);
567ec681f3Smrg	add_bucket(cache, 4096 * 2);
577ec681f3Smrg	add_bucket(cache, 4096 * 3);
587ec681f3Smrg
597ec681f3Smrg	/* Initialize the linked lists for BO reuse cache. */
607ec681f3Smrg	for (size = 4 * 4096; size <= cache_max_size; size *= 2) {
617ec681f3Smrg		add_bucket(cache, size);
627ec681f3Smrg		add_bucket(cache, size + size * 1 / 4);
637ec681f3Smrg		add_bucket(cache, size + size * 2 / 4);
647ec681f3Smrg		add_bucket(cache, size + size * 3 / 4);
657ec681f3Smrg	}
667ec681f3Smrg}
677ec681f3Smrg
687ec681f3Smrg/* Frees older cached buffers.  Called under etna_drm_table_lock */
697ec681f3Smrgvoid etna_bo_cache_cleanup(struct etna_bo_cache *cache, time_t time)
707ec681f3Smrg{
717ec681f3Smrg	unsigned i;
727ec681f3Smrg
737ec681f3Smrg	if (cache->time == time)
747ec681f3Smrg		return;
757ec681f3Smrg
767ec681f3Smrg	for (i = 0; i < cache->num_buckets; i++) {
777ec681f3Smrg		struct etna_bo_bucket *bucket = &cache->cache_bucket[i];
787ec681f3Smrg		struct etna_bo *bo;
797ec681f3Smrg
807ec681f3Smrg		while (!list_is_empty(&bucket->list)) {
817ec681f3Smrg			bo = LIST_ENTRY(struct etna_bo, bucket->list.next, list);
827ec681f3Smrg
837ec681f3Smrg			/* keep things in cache for at least 1 second: */
847ec681f3Smrg			if (time && ((time - bo->free_time) <= 1))
857ec681f3Smrg				break;
867ec681f3Smrg
877ec681f3Smrg			VG_BO_OBTAIN(bo);
887ec681f3Smrg			list_del(&bo->list);
897ec681f3Smrg			_etna_bo_del(bo);
907ec681f3Smrg		}
917ec681f3Smrg	}
927ec681f3Smrg
937ec681f3Smrg	cache->time = time;
947ec681f3Smrg}
957ec681f3Smrg
967ec681f3Smrgstatic struct etna_bo_bucket *get_bucket(struct etna_bo_cache *cache, uint32_t size)
977ec681f3Smrg{
987ec681f3Smrg	unsigned i;
997ec681f3Smrg
1007ec681f3Smrg	/* hmm, this is what intel does, but I suppose we could calculate our
1017ec681f3Smrg	 * way to the correct bucket size rather than looping..
1027ec681f3Smrg	 */
1037ec681f3Smrg	for (i = 0; i < cache->num_buckets; i++) {
1047ec681f3Smrg		struct etna_bo_bucket *bucket = &cache->cache_bucket[i];
1057ec681f3Smrg		if (bucket->size >= size) {
1067ec681f3Smrg			return bucket;
1077ec681f3Smrg		}
1087ec681f3Smrg	}
1097ec681f3Smrg
1107ec681f3Smrg	return NULL;
1117ec681f3Smrg}
1127ec681f3Smrg
1137ec681f3Smrgstatic int is_idle(struct etna_bo *bo)
1147ec681f3Smrg{
1157ec681f3Smrg	return etna_bo_cpu_prep(bo,
1167ec681f3Smrg			DRM_ETNA_PREP_READ |
1177ec681f3Smrg			DRM_ETNA_PREP_WRITE |
1187ec681f3Smrg			DRM_ETNA_PREP_NOSYNC) == 0;
1197ec681f3Smrg}
1207ec681f3Smrg
1217ec681f3Smrgstatic struct etna_bo *find_in_bucket(struct etna_bo_bucket *bucket, uint32_t flags)
1227ec681f3Smrg{
1237ec681f3Smrg	struct etna_bo *bo = NULL, *tmp;
1247ec681f3Smrg
1257ec681f3Smrg	simple_mtx_lock(&etna_drm_table_lock);
1267ec681f3Smrg
1277ec681f3Smrg	if (list_is_empty(&bucket->list))
1287ec681f3Smrg		goto out_unlock;
1297ec681f3Smrg
1307ec681f3Smrg	LIST_FOR_EACH_ENTRY_SAFE(bo, tmp, &bucket->list, list) {
1317ec681f3Smrg		/* skip BOs with different flags */
1327ec681f3Smrg		if (bo->flags != flags)
1337ec681f3Smrg			continue;
1347ec681f3Smrg
1357ec681f3Smrg		/* check if the first BO with matching flags is idle */
1367ec681f3Smrg		if (is_idle(bo)) {
1377ec681f3Smrg			list_delinit(&bo->list);
1387ec681f3Smrg			goto out_unlock;
1397ec681f3Smrg		}
1407ec681f3Smrg
1417ec681f3Smrg		/* If the oldest BO is still busy, don't try younger ones */
1427ec681f3Smrg		break;
1437ec681f3Smrg	}
1447ec681f3Smrg
1457ec681f3Smrg	/* There was no matching buffer found */
1467ec681f3Smrg	bo = NULL;
1477ec681f3Smrg
1487ec681f3Smrgout_unlock:
1497ec681f3Smrg	simple_mtx_unlock(&etna_drm_table_lock);
1507ec681f3Smrg
1517ec681f3Smrg	return bo;
1527ec681f3Smrg}
1537ec681f3Smrg
1547ec681f3Smrg/* allocate a new (un-tiled) buffer object
1557ec681f3Smrg *
1567ec681f3Smrg * NOTE: size is potentially rounded up to bucket size
1577ec681f3Smrg */
1587ec681f3Smrgstruct etna_bo *etna_bo_cache_alloc(struct etna_bo_cache *cache, uint32_t *size,
1597ec681f3Smrg    uint32_t flags)
1607ec681f3Smrg{
1617ec681f3Smrg	struct etna_bo *bo;
1627ec681f3Smrg	struct etna_bo_bucket *bucket;
1637ec681f3Smrg
1647ec681f3Smrg	*size = ALIGN(*size, 4096);
1657ec681f3Smrg	bucket = get_bucket(cache, *size);
1667ec681f3Smrg
1677ec681f3Smrg	/* see if we can be green and recycle: */
1687ec681f3Smrg	if (bucket) {
1697ec681f3Smrg		*size = bucket->size;
1707ec681f3Smrg		bo = find_in_bucket(bucket, flags);
1717ec681f3Smrg		if (bo) {
1727ec681f3Smrg			VG_BO_OBTAIN(bo);
1737ec681f3Smrg			p_atomic_set(&bo->refcnt, 1);
1747ec681f3Smrg			etna_device_ref(bo->dev);
1757ec681f3Smrg			return bo;
1767ec681f3Smrg		}
1777ec681f3Smrg	}
1787ec681f3Smrg
1797ec681f3Smrg	return NULL;
1807ec681f3Smrg}
1817ec681f3Smrg
1827ec681f3Smrgint etna_bo_cache_free(struct etna_bo_cache *cache, struct etna_bo *bo)
1837ec681f3Smrg{
1847ec681f3Smrg	struct etna_bo_bucket *bucket;
1857ec681f3Smrg
1867ec681f3Smrg	simple_mtx_assert_locked(&etna_drm_table_lock);
1877ec681f3Smrg
1887ec681f3Smrg	bucket = get_bucket(cache, bo->size);
1897ec681f3Smrg
1907ec681f3Smrg	/* see if we can be green and recycle: */
1917ec681f3Smrg	if (bucket) {
1927ec681f3Smrg		struct timespec time;
1937ec681f3Smrg
1947ec681f3Smrg		clock_gettime(CLOCK_MONOTONIC, &time);
1957ec681f3Smrg
1967ec681f3Smrg		bo->free_time = time.tv_sec;
1977ec681f3Smrg		VG_BO_RELEASE(bo);
1987ec681f3Smrg		list_addtail(&bo->list, &bucket->list);
1997ec681f3Smrg		etna_bo_cache_cleanup(cache, time.tv_sec);
2007ec681f3Smrg
2017ec681f3Smrg		/* bo's in the bucket cache don't have a ref and
2027ec681f3Smrg		 * don't hold a ref to the dev:
2037ec681f3Smrg		 */
2047ec681f3Smrg		etna_device_del_locked(bo->dev);
2057ec681f3Smrg
2067ec681f3Smrg		return 0;
2077ec681f3Smrg	}
2087ec681f3Smrg
2097ec681f3Smrg	return -1;
2107ec681f3Smrg}
211