linux_dsm_epyc7002/drivers/gpu/drm/i915/intel_wakeref.c
Chris Wilson c7302f2044 drm/i915: Defer final intel_wakeref_put to process context
As we need to acquire a mutex to serialise the final
intel_wakeref_put, we need to ensure that we are in process context at
that time. However, we want to allow operation on the intel_wakeref from
inside timer and other hardirq context, which means that need to defer
that final put to a workqueue.

Inside the final wakeref puts, we are safe to operate in any context, as
we are simply marking up the HW and state tracking for the potential
sleep. It's only the serialisation with the potential sleeping getting
that requires careful wait avoidance. This allows us to retain the
immediate processing as before (we only need to sleep over the same
races as the current mutex_lock).

v2: Add a selftest to ensure we exercise the code while lockdep watches.
v3: That test was extremely loud and complained about many things!
v4: Not a whale!

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=111295
References: https://bugs.freedesktop.org/show_bug.cgi?id=111245
References: https://bugs.freedesktop.org/show_bug.cgi?id=111256
Fixes: 18398904ca ("drm/i915: Only recover active engines")
Fixes: 51fbd8de87 ("drm/i915/pmu: Atomically acquire the gt_pm wakeref")
Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Cc: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Cc: Mika Kuoppala <mika.kuoppala@linux.intel.com>
Reviewed-by: Mika Kuoppala <mika.kuoppala@linux.intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20190808202758.10453-1-chris@chris-wilson.co.uk
2019-08-08 21:28:51 +01:00

180 lines
4.1 KiB
C

/*
* SPDX-License-Identifier: MIT
*
* Copyright © 2019 Intel Corporation
*/
#include <linux/wait_bit.h>
#include "intel_runtime_pm.h"
#include "intel_wakeref.h"
static void rpm_get(struct intel_wakeref *wf)
{
wf->wakeref = intel_runtime_pm_get(wf->rpm);
}
static void rpm_put(struct intel_wakeref *wf)
{
intel_wakeref_t wakeref = fetch_and_zero(&wf->wakeref);
intel_runtime_pm_put(wf->rpm, wakeref);
INTEL_WAKEREF_BUG_ON(!wakeref);
}
int __intel_wakeref_get_first(struct intel_wakeref *wf)
{
/*
* Treat get/put as different subclasses, as we may need to run
* the put callback from under the shrinker and do not want to
* cross-contanimate that callback with any extra work performed
* upon acquiring the wakeref.
*/
mutex_lock_nested(&wf->mutex, SINGLE_DEPTH_NESTING);
if (!atomic_read(&wf->count)) {
int err;
rpm_get(wf);
err = wf->ops->get(wf);
if (unlikely(err)) {
rpm_put(wf);
mutex_unlock(&wf->mutex);
return err;
}
smp_mb__before_atomic(); /* release wf->count */
}
atomic_inc(&wf->count);
mutex_unlock(&wf->mutex);
INTEL_WAKEREF_BUG_ON(atomic_read(&wf->count) <= 0);
return 0;
}
static void ____intel_wakeref_put_last(struct intel_wakeref *wf)
{
if (!atomic_dec_and_test(&wf->count))
goto unlock;
if (likely(!wf->ops->put(wf))) {
rpm_put(wf);
wake_up_var(&wf->wakeref);
} else {
/* ops->put() must schedule its own release on deferral */
atomic_set_release(&wf->count, 1);
}
unlock:
mutex_unlock(&wf->mutex);
}
void __intel_wakeref_put_last(struct intel_wakeref *wf)
{
INTEL_WAKEREF_BUG_ON(work_pending(&wf->work));
/* Assume we are not in process context and so cannot sleep. */
if (wf->ops->flags & INTEL_WAKEREF_PUT_ASYNC ||
!mutex_trylock(&wf->mutex)) {
schedule_work(&wf->work);
return;
}
____intel_wakeref_put_last(wf);
}
static void __intel_wakeref_put_work(struct work_struct *wrk)
{
struct intel_wakeref *wf = container_of(wrk, typeof(*wf), work);
if (atomic_add_unless(&wf->count, -1, 1))
return;
mutex_lock(&wf->mutex);
____intel_wakeref_put_last(wf);
}
void __intel_wakeref_init(struct intel_wakeref *wf,
struct intel_runtime_pm *rpm,
const struct intel_wakeref_ops *ops,
struct lock_class_key *key)
{
wf->rpm = rpm;
wf->ops = ops;
__mutex_init(&wf->mutex, "wakeref", key);
atomic_set(&wf->count, 0);
wf->wakeref = 0;
INIT_WORK(&wf->work, __intel_wakeref_put_work);
}
int intel_wakeref_wait_for_idle(struct intel_wakeref *wf)
{
return wait_var_event_killable(&wf->wakeref,
!intel_wakeref_is_active(wf));
}
static void wakeref_auto_timeout(struct timer_list *t)
{
struct intel_wakeref_auto *wf = from_timer(wf, t, timer);
intel_wakeref_t wakeref;
unsigned long flags;
if (!refcount_dec_and_lock_irqsave(&wf->count, &wf->lock, &flags))
return;
wakeref = fetch_and_zero(&wf->wakeref);
spin_unlock_irqrestore(&wf->lock, flags);
intel_runtime_pm_put(wf->rpm, wakeref);
}
void intel_wakeref_auto_init(struct intel_wakeref_auto *wf,
struct intel_runtime_pm *rpm)
{
spin_lock_init(&wf->lock);
timer_setup(&wf->timer, wakeref_auto_timeout, 0);
refcount_set(&wf->count, 0);
wf->wakeref = 0;
wf->rpm = rpm;
}
void intel_wakeref_auto(struct intel_wakeref_auto *wf, unsigned long timeout)
{
unsigned long flags;
if (!timeout) {
if (del_timer_sync(&wf->timer))
wakeref_auto_timeout(&wf->timer);
return;
}
/* Our mission is that we only extend an already active wakeref */
assert_rpm_wakelock_held(wf->rpm);
if (!refcount_inc_not_zero(&wf->count)) {
spin_lock_irqsave(&wf->lock, flags);
if (!refcount_inc_not_zero(&wf->count)) {
INTEL_WAKEREF_BUG_ON(wf->wakeref);
wf->wakeref = intel_runtime_pm_get_if_in_use(wf->rpm);
refcount_set(&wf->count, 1);
}
spin_unlock_irqrestore(&wf->lock, flags);
}
/*
* If we extend a pending timer, we will only get a single timer
* callback and so need to cancel the local inc by running the
* elided callback to keep the wf->count balanced.
*/
if (mod_timer(&wf->timer, jiffies + timeout))
wakeref_auto_timeout(&wf->timer);
}
void intel_wakeref_auto_fini(struct intel_wakeref_auto *wf)
{
intel_wakeref_auto(wf, 0);
INTEL_WAKEREF_BUG_ON(wf->wakeref);
}