summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/i915/gt/intel_gt_requests.c
blob: a0112ba08ca7796a65542400fd024acb36c4ef49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*
 * SPDX-License-Identifier: MIT
 *
 * Copyright © 2019 Intel Corporation
 */

#include "i915_drv.h" /* for_each_engine() */
#include "i915_request.h"
#include "intel_gt.h"
#include "intel_gt_pm.h"
#include "intel_gt_requests.h"
#include "intel_timeline.h"

static void 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))
			break;
}

static void flush_submission(struct intel_gt *gt)
{
	struct intel_engine_cs *engine;
	enum intel_engine_id id;

	for_each_engine(engine, gt, id)
		intel_engine_flush_submission(engine);
}

long intel_gt_retire_requests_timeout(struct intel_gt *gt, long timeout)
{
	struct intel_gt_timelines *timelines = &gt->timelines;
	struct intel_timeline *tl, *tn;
	unsigned long active_count = 0;
	unsigned long flags;
	bool interruptible;
	LIST_HEAD(free);

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

	flush_submission(gt); /* kick the ksoftirqd tasklets */

	spin_lock_irqsave(&timelines->lock, flags);
	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_irqrestore(&timelines->lock, flags);

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

			fence = i915_active_fence_get(&tl->last_request);
			if (fence) {
				timeout = dma_fence_wait_timeout(fence,
								 interruptible,
								 timeout);
				dma_fence_put(fence);
			}
		}

		retire_requests(tl);

		spin_lock_irqsave(&timelines->lock, flags);

		/* Resume iteration after dropping lock */
		list_safe_reset_next(tl, tn, link);
		if (atomic_dec_and_test(&tl->active_count))
			list_del(&tl->link);
		else
			active_count += !!rcu_access_pointer(tl->last_request.fence);

		mutex_unlock(&tl->mutex);

		/* 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_irqrestore(&timelines->lock, flags);

	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);

	intel_gt_retire_requests(gt);
	schedule_delayed_work(&gt->requests.retire_work,
			      round_jiffies_up_relative(HZ));
}

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));
}