intel_gt_requests.c 5.7 KB
Newer Older
1 2 3 4 5 6
/*
 * SPDX-License-Identifier: MIT
 *
 * Copyright © 2019 Intel Corporation
 */

7 8
#include <linux/workqueue.h>

9
#include "i915_drv.h" /* for_each_engine() */
10
#include "i915_request.h"
11
#include "intel_engine_heartbeat.h"
12 13 14 15 16
#include "intel_gt.h"
#include "intel_gt_pm.h"
#include "intel_gt_requests.h"
#include "intel_timeline.h"

17
static bool retire_requests(struct intel_timeline *tl)
18 19 20 21 22
{
	struct i915_request *rq, *rn;

	list_for_each_entry_safe(rq, rn, &tl->requests, link)
		if (!i915_request_retire(rq))
23 24 25 26
			return false;

	/* And check nothing new was submitted */
	return !i915_active_fence_isset(&tl->last_request);
27 28
}

29
static bool flush_submission(struct intel_gt *gt)
30 31 32
{
	struct intel_engine_cs *engine;
	enum intel_engine_id id;
33
	bool active = false;
34

35
	if (!intel_gt_pm_is_awake(gt))
36
		return false;
37

38
	for_each_engine(engine, gt, id) {
39
		intel_engine_flush_submission(engine);
40
		active |= flush_work(&engine->retire_work);
41
		active |= flush_delayed_work(&engine->wakeref.work);
42
	}
43 44

	return active;
45 46
}

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
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)
{
78
#define STUB ((struct intel_timeline *)1)
79 80 81 82 83 84 85 86
	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.
	 */

87
	if (cmpxchg(&tl->retire, NULL, STUB)) /* already queued */
88 89 90 91 92 93 94 95 96 97 98 99 100 101
		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)
{
102 103 104
	/* We don't deal well with the engine disappearing beneath us */
	GEM_BUG_ON(intel_engine_is_virtual(engine));

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
	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);
}

120 121 122 123
long intel_gt_retire_requests_timeout(struct intel_gt *gt, long timeout)
{
	struct intel_gt_timelines *timelines = &gt->timelines;
	struct intel_timeline *tl, *tn;
124
	unsigned long active_count = 0;
125 126 127 128 129 130 131
	bool interruptible;
	LIST_HEAD(free);

	interruptible = true;
	if (unlikely(timeout < 0))
		timeout = -timeout, interruptible = false;

132
	flush_submission(gt); /* kick the ksoftirqd tasklets */
133
	spin_lock(&timelines->lock);
134
	list_for_each_entry_safe(tl, tn, &timelines->active_list, link) {
135 136
		if (!mutex_trylock(&tl->mutex)) {
			active_count++; /* report busy to caller, try again? */
137
			continue;
138
		}
139 140

		intel_timeline_get(tl);
141 142
		GEM_BUG_ON(!atomic_read(&tl->active_count));
		atomic_inc(&tl->active_count); /* pin the list element */
143
		spin_unlock(&timelines->lock);
144 145 146 147 148 149

		if (timeout > 0) {
			struct dma_fence *fence;

			fence = i915_active_fence_get(&tl->last_request);
			if (fence) {
150 151
				mutex_unlock(&tl->mutex);

152
				timeout = dma_fence_wait_timeout(fence,
153
								 interruptible,
154 155
								 timeout);
				dma_fence_put(fence);
156 157 158 159 160 161

				/* Retirement is best effort */
				if (!mutex_trylock(&tl->mutex)) {
					active_count++;
					goto out_active;
				}
162 163 164
			}
		}

165 166
		if (!retire_requests(tl) || flush_submission(gt))
			active_count++;
167
		mutex_unlock(&tl->mutex);
168

169
out_active:	spin_lock(&timelines->lock);
170

171
		/* Resume list iteration after reacquiring spinlock */
172
		list_safe_reset_next(tl, tn, link);
173
		if (atomic_dec_and_test(&tl->active_count))
174 175 176 177 178
			list_del(&tl->link);


		/* Defer the final release to after the spinlock */
		if (refcount_dec_and_test(&tl->kref.refcount)) {
179
			GEM_BUG_ON(atomic_read(&tl->active_count));
180 181 182
			list_add(&tl->link, &free);
		}
	}
183
	spin_unlock(&timelines->lock);
184 185 186 187

	list_for_each_entry_safe(tl, tn, &free, link)
		__intel_timeline_free(&tl->kref);

188
	return active_count ? timeout : 0;
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
}

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(&gt->requests.retire_work,
			      round_jiffies_up_relative(HZ));
213
	intel_gt_retire_requests(gt);
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
}

void intel_gt_init_requests(struct intel_gt *gt)
{
	INIT_DELAYED_WORK(&gt->requests.retire_work, retire_work_handler);
}

void intel_gt_park_requests(struct intel_gt *gt)
{
	cancel_delayed_work(&gt->requests.retire_work);
}

void intel_gt_unpark_requests(struct intel_gt *gt)
{
	schedule_delayed_work(&gt->requests.retire_work,
			      round_jiffies_up_relative(HZ));
}
231 232 233 234 235 236

void intel_gt_fini_requests(struct intel_gt *gt)
{
	/* Wait until the work is marked as finished before unloading! */
	cancel_delayed_work_sync(&gt->requests.retire_work);
}