| /* |
| * SPDX-License-Identifier: MIT |
| * |
| * Copyright © 2019 Intel Corporation |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/workqueue.h> |
| |
| #include "i915_active.h" |
| #include "gem/i915_gem_context.h" |
| #include "gem/i915_gem_object.h" |
| #include "i915_globals.h" |
| #include "i915_request.h" |
| #include "i915_scheduler.h" |
| #include "i915_vma.h" |
| |
| static LIST_HEAD(globals); |
| |
| static atomic_t active; |
| static atomic_t epoch; |
| static struct park_work { |
| struct rcu_work work; |
| int epoch; |
| } park; |
| |
| static void i915_globals_shrink(void) |
| { |
| struct i915_global *global; |
| |
| /* |
| * kmem_cache_shrink() discards empty slabs and reorders partially |
| * filled slabs to prioritise allocating from the mostly full slabs, |
| * with the aim of reducing fragmentation. |
| */ |
| list_for_each_entry(global, &globals, link) |
| global->shrink(); |
| } |
| |
| static void __i915_globals_park(struct work_struct *work) |
| { |
| /* Confirm nothing woke up in the last grace period */ |
| if (park.epoch == atomic_read(&epoch)) |
| i915_globals_shrink(); |
| } |
| |
| void __init i915_global_register(struct i915_global *global) |
| { |
| GEM_BUG_ON(!global->shrink); |
| GEM_BUG_ON(!global->exit); |
| |
| list_add_tail(&global->link, &globals); |
| } |
| |
| static void __i915_globals_cleanup(void) |
| { |
| struct i915_global *global, *next; |
| |
| list_for_each_entry_safe_reverse(global, next, &globals, link) |
| global->exit(); |
| } |
| |
| static __initconst int (* const initfn[])(void) = { |
| i915_global_active_init, |
| i915_global_context_init, |
| i915_global_gem_context_init, |
| i915_global_objects_init, |
| i915_global_request_init, |
| i915_global_scheduler_init, |
| i915_global_vma_init, |
| }; |
| |
| int __init i915_globals_init(void) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(initfn); i++) { |
| int err; |
| |
| err = initfn[i](); |
| if (err) { |
| __i915_globals_cleanup(); |
| return err; |
| } |
| } |
| |
| INIT_RCU_WORK(&park.work, __i915_globals_park); |
| return 0; |
| } |
| |
| void i915_globals_park(void) |
| { |
| /* |
| * Defer shrinking the global slab caches (and other work) until |
| * after a RCU grace period has completed with no activity. This |
| * is to try and reduce the latency impact on the consumers caused |
| * by us shrinking the caches the same time as they are trying to |
| * allocate, with the assumption being that if we idle long enough |
| * for an RCU grace period to elapse since the last use, it is likely |
| * to be longer until we need the caches again. |
| */ |
| if (!atomic_dec_and_test(&active)) |
| return; |
| |
| park.epoch = atomic_inc_return(&epoch); |
| queue_rcu_work(system_wq, &park.work); |
| } |
| |
| void i915_globals_unpark(void) |
| { |
| atomic_inc(&epoch); |
| atomic_inc(&active); |
| } |
| |
| void __exit i915_globals_exit(void) |
| { |
| /* Flush any residual park_work */ |
| atomic_inc(&epoch); |
| flush_rcu_work(&park.work); |
| |
| __i915_globals_cleanup(); |
| |
| /* And ensure that our DESTROY_BY_RCU slabs are truly destroyed */ |
| rcu_barrier(); |
| } |