mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

As we have pinned the timeline (using tl->active_count), we can safely drop the tl->mutex as we wait for what we believe to be the final request on that timeline. This is useful for ensuring that we do not block the engine heartbeat by hogging the kernel_context's timeline on a dead GPU. References: https://gitlab.freedesktop.org/drm/intel/issues/1364 Fixes:058179e72e
("drm/i915/gt: Replace hangcheck by heartbeats") Fixes:f33a8a5160
("drm/i915: Merge wait_for_timelines with retire_request") Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> Cc: Mika Kuoppala <mika.kuoppala@linux.intel.com> Reviewed-by: Mika Kuoppala <mika.kuoppala@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200303140009.1494819-1-chris@chris-wilson.co.uk
236 lines
5.7 KiB
C
236 lines
5.7 KiB
C
/*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
* Copyright © 2019 Intel Corporation
|
|
*/
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include "i915_drv.h" /* for_each_engine() */
|
|
#include "i915_request.h"
|
|
#include "intel_engine_heartbeat.h"
|
|
#include "intel_gt.h"
|
|
#include "intel_gt_pm.h"
|
|
#include "intel_gt_requests.h"
|
|
#include "intel_timeline.h"
|
|
|
|
static bool retire_requests(struct intel_timeline *tl)
|
|
{
|
|
struct i915_request *rq, *rn;
|
|
|
|
list_for_each_entry_safe(rq, rn, &tl->requests, link)
|
|
if (!i915_request_retire(rq))
|
|
return false;
|
|
|
|
/* And check nothing new was submitted */
|
|
return !i915_active_fence_isset(&tl->last_request);
|
|
}
|
|
|
|
static bool flush_submission(struct intel_gt *gt)
|
|
{
|
|
struct intel_engine_cs *engine;
|
|
enum intel_engine_id id;
|
|
bool active = false;
|
|
|
|
if (!intel_gt_pm_is_awake(gt))
|
|
return false;
|
|
|
|
for_each_engine(engine, gt, id) {
|
|
intel_engine_flush_submission(engine);
|
|
active |= flush_work(&engine->retire_work);
|
|
active |= flush_work(&engine->wakeref.work);
|
|
}
|
|
|
|
return active;
|
|
}
|
|
|
|
static void engine_retire(struct work_struct *work)
|
|
{
|
|
struct intel_engine_cs *engine =
|
|
container_of(work, typeof(*engine), retire_work);
|
|
struct intel_timeline *tl = xchg(&engine->retire, NULL);
|
|
|
|
do {
|
|
struct intel_timeline *next = xchg(&tl->retire, NULL);
|
|
|
|
/*
|
|
* Our goal here is to retire _idle_ timelines as soon as
|
|
* possible (as they are idle, we do not expect userspace
|
|
* to be cleaning up anytime soon).
|
|
*
|
|
* If the timeline is currently locked, either it is being
|
|
* retired elsewhere or about to be!
|
|
*/
|
|
if (mutex_trylock(&tl->mutex)) {
|
|
retire_requests(tl);
|
|
mutex_unlock(&tl->mutex);
|
|
}
|
|
intel_timeline_put(tl);
|
|
|
|
GEM_BUG_ON(!next);
|
|
tl = ptr_mask_bits(next, 1);
|
|
} while (tl);
|
|
}
|
|
|
|
static bool add_retire(struct intel_engine_cs *engine,
|
|
struct intel_timeline *tl)
|
|
{
|
|
#define STUB ((struct intel_timeline *)1)
|
|
struct intel_timeline *first;
|
|
|
|
/*
|
|
* We open-code a llist here to include the additional tag [BIT(0)]
|
|
* so that we know when the timeline is already on a
|
|
* retirement queue: either this engine or another.
|
|
*/
|
|
|
|
if (cmpxchg(&tl->retire, NULL, STUB)) /* already queued */
|
|
return false;
|
|
|
|
intel_timeline_get(tl);
|
|
first = READ_ONCE(engine->retire);
|
|
do
|
|
tl->retire = ptr_pack_bits(first, 1, 1);
|
|
while (!try_cmpxchg(&engine->retire, &first, tl));
|
|
|
|
return !first;
|
|
}
|
|
|
|
void intel_engine_add_retire(struct intel_engine_cs *engine,
|
|
struct intel_timeline *tl)
|
|
{
|
|
/* We don't deal well with the engine disappearing beneath us */
|
|
GEM_BUG_ON(intel_engine_is_virtual(engine));
|
|
|
|
if (add_retire(engine, tl))
|
|
schedule_work(&engine->retire_work);
|
|
}
|
|
|
|
void intel_engine_init_retire(struct intel_engine_cs *engine)
|
|
{
|
|
INIT_WORK(&engine->retire_work, engine_retire);
|
|
}
|
|
|
|
void intel_engine_fini_retire(struct intel_engine_cs *engine)
|
|
{
|
|
flush_work(&engine->retire_work);
|
|
GEM_BUG_ON(engine->retire);
|
|
}
|
|
|
|
long intel_gt_retire_requests_timeout(struct intel_gt *gt, long timeout)
|
|
{
|
|
struct intel_gt_timelines *timelines = >->timelines;
|
|
struct intel_timeline *tl, *tn;
|
|
unsigned long active_count = 0;
|
|
bool interruptible;
|
|
LIST_HEAD(free);
|
|
|
|
interruptible = true;
|
|
if (unlikely(timeout < 0))
|
|
timeout = -timeout, interruptible = false;
|
|
|
|
flush_submission(gt); /* kick the ksoftirqd tasklets */
|
|
spin_lock(&timelines->lock);
|
|
list_for_each_entry_safe(tl, tn, &timelines->active_list, link) {
|
|
if (!mutex_trylock(&tl->mutex)) {
|
|
active_count++; /* report busy to caller, try again? */
|
|
continue;
|
|
}
|
|
|
|
intel_timeline_get(tl);
|
|
GEM_BUG_ON(!atomic_read(&tl->active_count));
|
|
atomic_inc(&tl->active_count); /* pin the list element */
|
|
spin_unlock(&timelines->lock);
|
|
|
|
if (timeout > 0) {
|
|
struct dma_fence *fence;
|
|
|
|
fence = i915_active_fence_get(&tl->last_request);
|
|
if (fence) {
|
|
mutex_unlock(&tl->mutex);
|
|
|
|
timeout = dma_fence_wait_timeout(fence,
|
|
interruptible,
|
|
timeout);
|
|
dma_fence_put(fence);
|
|
|
|
/* Retirement is best effort */
|
|
if (!mutex_trylock(&tl->mutex)) {
|
|
active_count++;
|
|
goto out_active;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!retire_requests(tl) || flush_submission(gt))
|
|
active_count++;
|
|
mutex_unlock(&tl->mutex);
|
|
|
|
out_active: spin_lock(&timelines->lock);
|
|
|
|
/* Resume list iteration after reacquiring spinlock */
|
|
list_safe_reset_next(tl, tn, link);
|
|
if (atomic_dec_and_test(&tl->active_count))
|
|
list_del(&tl->link);
|
|
|
|
|
|
/* Defer the final release to after the spinlock */
|
|
if (refcount_dec_and_test(&tl->kref.refcount)) {
|
|
GEM_BUG_ON(atomic_read(&tl->active_count));
|
|
list_add(&tl->link, &free);
|
|
}
|
|
}
|
|
spin_unlock(&timelines->lock);
|
|
|
|
list_for_each_entry_safe(tl, tn, &free, link)
|
|
__intel_timeline_free(&tl->kref);
|
|
|
|
return active_count ? timeout : 0;
|
|
}
|
|
|
|
int intel_gt_wait_for_idle(struct intel_gt *gt, long timeout)
|
|
{
|
|
/* If the device is asleep, we have no requests outstanding */
|
|
if (!intel_gt_pm_is_awake(gt))
|
|
return 0;
|
|
|
|
while ((timeout = intel_gt_retire_requests_timeout(gt, timeout)) > 0) {
|
|
cond_resched();
|
|
if (signal_pending(current))
|
|
return -EINTR;
|
|
}
|
|
|
|
return timeout;
|
|
}
|
|
|
|
static void retire_work_handler(struct work_struct *work)
|
|
{
|
|
struct intel_gt *gt =
|
|
container_of(work, typeof(*gt), requests.retire_work.work);
|
|
|
|
schedule_delayed_work(>->requests.retire_work,
|
|
round_jiffies_up_relative(HZ));
|
|
intel_gt_retire_requests(gt);
|
|
}
|
|
|
|
void intel_gt_init_requests(struct intel_gt *gt)
|
|
{
|
|
INIT_DELAYED_WORK(>->requests.retire_work, retire_work_handler);
|
|
}
|
|
|
|
void intel_gt_park_requests(struct intel_gt *gt)
|
|
{
|
|
cancel_delayed_work(>->requests.retire_work);
|
|
}
|
|
|
|
void intel_gt_unpark_requests(struct intel_gt *gt)
|
|
{
|
|
schedule_delayed_work(>->requests.retire_work,
|
|
round_jiffies_up_relative(HZ));
|
|
}
|
|
|
|
void intel_gt_fini_requests(struct intel_gt *gt)
|
|
{
|
|
/* Wait until the work is marked as finished before unloading! */
|
|
cancel_delayed_work_sync(>->requests.retire_work);
|
|
}
|