|  | /* | 
|  | * 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; | 
|  | } |