/* * SPDX-License-Identifier: MIT * * Copyright © 2019 Intel Corporation */ #include "i915_drv.h" #include "i915_active.h" #include "i915_globals.h" #define BKL(ref) (&(ref)->i915->drm.struct_mutex) /* * Active refs memory management * * To be more economical with memory, we reap all the i915_active trees as * they idle (when we know the active requests are inactive) and allocate the * nodes from a local slab cache to hopefully reduce the fragmentation. */ static struct i915_global_active { struct i915_global base; struct kmem_cache *slab_cache; } global; struct active_node { struct i915_active_request base; struct i915_active *ref; struct rb_node node; u64 timeline; }; static void __active_park(struct i915_active *ref) { struct active_node *it, *n; rbtree_postorder_for_each_entry_safe(it, n, &ref->tree, node) { GEM_BUG_ON(i915_active_request_isset(&it->base)); kmem_cache_free(global.slab_cache, it); } ref->tree = RB_ROOT; } static void __active_retire(struct i915_active *ref) { GEM_BUG_ON(!ref->count); if (--ref->count) return; /* return the unused nodes to our slabcache */ __active_park(ref); ref->retire(ref); } static void node_retire(struct i915_active_request *base, struct i915_request *rq) { __active_retire(container_of(base, struct active_node, base)->ref); } static void last_retire(struct i915_active_request *base, struct i915_request *rq) { __active_retire(container_of(base, struct i915_active, last)); } static struct i915_active_request * active_instance(struct i915_active *ref, u64 idx) { struct active_node *node; struct rb_node **p, *parent; struct i915_request *old; /* * We track the most recently used timeline to skip a rbtree search * for the common case, under typical loads we never need the rbtree * at all. We can reuse the last slot if it is empty, that is * after the previous activity has been retired, or if it matches the * current timeline. * * Note that we allow the timeline to be active simultaneously in * the rbtree and the last cache. We do this to avoid having * to search and replace the rbtree element for a new timeline, with * the cost being that we must be aware that the ref may be retired * twice for the same timeline (as the older rbtree element will be * retired before the new request added to last). */ old = i915_active_request_raw(&ref->last, BKL(ref)); if (!old || old->fence.context == idx) goto out; /* Move the currently active fence into the rbtree */ idx = old->fence.context; parent = NULL; p = &ref->tree.rb_node; while (*p) { parent = *p; node = rb_entry(parent, struct active_node, node); if (node->timeline == idx) goto replace; if (node->timeline < idx) p = &parent->rb_right; else p = &parent->rb_left; } node = kmem_cache_alloc(global.slab_cache, GFP_KERNEL); /* kmalloc may retire the ref->last (thanks shrinker)! */ if (unlikely(!i915_active_request_raw(&ref->last, BKL(ref)))) { kmem_cache_free(global.slab_cache, node); goto out; } if (unlikely(!node)) return ERR_PTR(-ENOMEM); i915_active_request_init(&node->base, NULL, node_retire); node->ref = ref; node->timeline = idx; rb_link_node(&node->node, parent, p); rb_insert_color(&node->node, &ref->tree); replace: /* * Overwrite the previous active slot in the rbtree with last, * leaving last zeroed. If the previous slot is still active, * we must be careful as we now only expect to receive one retire * callback not two, and so much undo the active counting for the * overwritten slot. */ if (i915_active_request_isset(&node->base)) { /* Retire ourselves from the old rq->active_list */ __list_del_entry(&node->base.link); ref->count--; GEM_BUG_ON(!ref->count); } GEM_BUG_ON(list_empty(&ref->last.link)); list_replace_init(&ref->last.link, &node->base.link); node->base.request = fetch_and_zero(&ref->last.request); out: return &ref->last; } void i915_active_init(struct drm_i915_private *i915, struct i915_active *ref, void (*retire)(struct i915_active *ref)) { ref->i915 = i915; ref->retire = retire; ref->tree = RB_ROOT; i915_active_request_init(&ref->last, NULL, last_retire); ref->count = 0; } int i915_active_ref(struct i915_active *ref, u64 timeline, struct i915_request *rq) { struct i915_active_request *active; int err = 0; /* Prevent reaping in case we malloc/wait while building the tree */ i915_active_acquire(ref); active = active_instance(ref, timeline); if (IS_ERR(active)) { err = PTR_ERR(active); goto out; } if (!i915_active_request_isset(active)) ref->count++; __i915_active_request_set(active, rq); GEM_BUG_ON(!ref->count); out: i915_active_release(ref); return err; } bool i915_active_acquire(struct i915_active *ref) { lockdep_assert_held(BKL(ref)); return !ref->count++; } void i915_active_release(struct i915_active *ref) { lockdep_assert_held(BKL(ref)); __active_retire(ref); } int i915_active_wait(struct i915_active *ref) { struct active_node *it, *n; int ret = 0; if (i915_active_acquire(ref)) goto out_release; ret = i915_active_request_retire(&ref->last, BKL(ref)); if (ret) goto out_release; rbtree_postorder_for_each_entry_safe(it, n, &ref->tree, node) { ret = i915_active_request_retire(&it->base, BKL(ref)); if (ret) break; } out_release: i915_active_release(ref); return ret; } int i915_request_await_active_request(struct i915_request *rq, struct i915_active_request *active) { struct i915_request *barrier = i915_active_request_raw(active, &rq->i915->drm.struct_mutex); return barrier ? i915_request_await_dma_fence(rq, &barrier->fence) : 0; } int i915_request_await_active(struct i915_request *rq, struct i915_active *ref) { struct active_node *it, *n; int err = 0; /* await allocates and so we need to avoid hitting the shrinker */ if (i915_active_acquire(ref)) goto out; /* was idle */ err = i915_request_await_active_request(rq, &ref->last); if (err) goto out; rbtree_postorder_for_each_entry_safe(it, n, &ref->tree, node) { err = i915_request_await_active_request(rq, &it->base); if (err) goto out; } out: i915_active_release(ref); return err; } #if IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM) void i915_active_fini(struct i915_active *ref) { GEM_BUG_ON(i915_active_request_isset(&ref->last)); GEM_BUG_ON(!RB_EMPTY_ROOT(&ref->tree)); GEM_BUG_ON(ref->count); } #endif int i915_active_request_set(struct i915_active_request *active, struct i915_request *rq) { int err; /* Must maintain ordering wrt previous active requests */ err = i915_request_await_active_request(rq, active); if (err) return err; __i915_active_request_set(active, rq); return 0; } void i915_active_retire_noop(struct i915_active_request *active, struct i915_request *request) { /* Space left intentionally blank */ } #if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) #include "selftests/i915_active.c" #endif static void i915_global_active_shrink(void) { kmem_cache_shrink(global.slab_cache); } static void i915_global_active_exit(void) { kmem_cache_destroy(global.slab_cache); } static struct i915_global_active global = { { .shrink = i915_global_active_shrink, .exit = i915_global_active_exit, } }; int __init i915_global_active_init(void) { global.slab_cache = KMEM_CACHE(active_node, SLAB_HWCACHE_ALIGN); if (!global.slab_cache) return -ENOMEM; i915_global_register(&global.base); return 0; }