Skip to content

Commit 8ee36e0

Browse files
committed
drm/i915/execlists: Minimalistic timeslicing
If we have multiple contexts of equal priority pending execution, activate a timer to demote the currently executing context in favour of the next in the queue when that timeslice expires. This enforces fairness between contexts (so long as they allow preemption -- forced preemption, in the future, will kick those who do not obey) and allows us to avoid userspace blocking forward progress with e.g. unbounded MI_SEMAPHORE_WAIT. For the starting point here, we use the jiffie as our timeslice so that we should be reasonably efficient wrt frequent CPU wakeups. Testcase: igt/gem_exec_scheduler/semaphore-resolve Signed-off-by: Chris Wilson <[email protected]> Reviewed-by: Mika Kuoppala <[email protected]> Link: https://patchwork.freedesktop.org/patch/msgid/[email protected]
1 parent 22b7a42 commit 8ee36e0

File tree

5 files changed

+347
-0
lines changed

5 files changed

+347
-0
lines changed

drivers/gpu/drm/i915/gt/intel_engine_types.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <linux/kref.h>
1313
#include <linux/list.h>
1414
#include <linux/llist.h>
15+
#include <linux/timer.h>
1516
#include <linux/types.h>
1617

1718
#include "i915_gem.h"
@@ -149,6 +150,11 @@ struct intel_engine_execlists {
149150
*/
150151
struct tasklet_struct tasklet;
151152

153+
/**
154+
* @timer: kick the current context if its timeslice expires
155+
*/
156+
struct timer_list timer;
157+
152158
/**
153159
* @default_priolist: priority list for I915_PRIORITY_NORMAL
154160
*/

drivers/gpu/drm/i915/gt/intel_lrc.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ static int effective_prio(const struct i915_request *rq)
266266
prio |= I915_PRIORITY_NOSEMAPHORE;
267267

268268
/* Restrict mere WAIT boosts from triggering preemption */
269+
BUILD_BUG_ON(__NO_PREEMPTION & ~I915_PRIORITY_MASK); /* only internal */
269270
return prio | __NO_PREEMPTION;
270271
}
271272

@@ -830,6 +831,81 @@ last_active(const struct intel_engine_execlists *execlists)
830831
return *last;
831832
}
832833

834+
static void
835+
defer_request(struct i915_request * const rq, struct list_head * const pl)
836+
{
837+
struct i915_dependency *p;
838+
839+
/*
840+
* We want to move the interrupted request to the back of
841+
* the round-robin list (i.e. its priority level), but
842+
* in doing so, we must then move all requests that were in
843+
* flight and were waiting for the interrupted request to
844+
* be run after it again.
845+
*/
846+
list_move_tail(&rq->sched.link, pl);
847+
848+
list_for_each_entry(p, &rq->sched.waiters_list, wait_link) {
849+
struct i915_request *w =
850+
container_of(p->waiter, typeof(*w), sched);
851+
852+
/* Leave semaphores spinning on the other engines */
853+
if (w->engine != rq->engine)
854+
continue;
855+
856+
/* No waiter should start before the active request completed */
857+
GEM_BUG_ON(i915_request_started(w));
858+
859+
GEM_BUG_ON(rq_prio(w) > rq_prio(rq));
860+
if (rq_prio(w) < rq_prio(rq))
861+
continue;
862+
863+
if (list_empty(&w->sched.link))
864+
continue; /* Not yet submitted; unready */
865+
866+
/*
867+
* This should be very shallow as it is limited by the
868+
* number of requests that can fit in a ring (<64) and
869+
* the number of contexts that can be in flight on this
870+
* engine.
871+
*/
872+
defer_request(w, pl);
873+
}
874+
}
875+
876+
static void defer_active(struct intel_engine_cs *engine)
877+
{
878+
struct i915_request *rq;
879+
880+
rq = __unwind_incomplete_requests(engine);
881+
if (!rq)
882+
return;
883+
884+
defer_request(rq, i915_sched_lookup_priolist(engine, rq_prio(rq)));
885+
}
886+
887+
static bool
888+
need_timeslice(struct intel_engine_cs *engine, const struct i915_request *rq)
889+
{
890+
int hint;
891+
892+
if (list_is_last(&rq->sched.link, &engine->active.requests))
893+
return false;
894+
895+
hint = max(rq_prio(list_next_entry(rq, sched.link)),
896+
engine->execlists.queue_priority_hint);
897+
898+
return hint >= rq_prio(rq);
899+
}
900+
901+
static bool
902+
enable_timeslice(struct intel_engine_cs *engine)
903+
{
904+
struct i915_request *last = last_active(&engine->execlists);
905+
906+
return last && need_timeslice(engine, last);
907+
}
908+
833909
static void execlists_dequeue(struct intel_engine_cs *engine)
834910
{
835911
struct intel_engine_execlists * const execlists = &engine->execlists;
@@ -923,6 +999,32 @@ static void execlists_dequeue(struct intel_engine_cs *engine)
923999
*/
9241000
last->hw_context->lrc_desc |= CTX_DESC_FORCE_RESTORE;
9251001
last = NULL;
1002+
} else if (need_timeslice(engine, last) &&
1003+
!timer_pending(&engine->execlists.timer)) {
1004+
GEM_TRACE("%s: expired last=%llx:%lld, prio=%d, hint=%d\n",
1005+
engine->name,
1006+
last->fence.context,
1007+
last->fence.seqno,
1008+
last->sched.attr.priority,
1009+
execlists->queue_priority_hint);
1010+
1011+
ring_set_paused(engine, 1);
1012+
defer_active(engine);
1013+
1014+
/*
1015+
* Unlike for preemption, if we rewind and continue
1016+
* executing the same context as previously active,
1017+
* the order of execution will remain the same and
1018+
* the tail will only advance. We do not need to
1019+
* force a full context restore, as a lite-restore
1020+
* is sufficient to resample the monotonic TAIL.
1021+
*
1022+
* If we switch to any other context, similarly we
1023+
* will not rewind TAIL of current context, and
1024+
* normal save/restore will preserve state and allow
1025+
* us to later continue executing the same request.
1026+
*/
1027+
last = NULL;
9261028
} else {
9271029
/*
9281030
* Otherwise if we already have a request pending
@@ -1247,6 +1349,9 @@ static void process_csb(struct intel_engine_cs *engine)
12471349
sizeof(*execlists->pending));
12481350
execlists->pending[0] = NULL;
12491351

1352+
if (enable_timeslice(engine))
1353+
mod_timer(&execlists->timer, jiffies + 1);
1354+
12501355
if (!inject_preempt_hang(execlists))
12511356
ring_set_paused(engine, 0);
12521357
} else if (status & GEN8_CTX_STATUS_PREEMPTED) {
@@ -1317,6 +1422,15 @@ static void execlists_submission_tasklet(unsigned long data)
13171422
spin_unlock_irqrestore(&engine->active.lock, flags);
13181423
}
13191424

1425+
static void execlists_submission_timer(struct timer_list *timer)
1426+
{
1427+
struct intel_engine_cs *engine =
1428+
from_timer(engine, timer, execlists.timer);
1429+
1430+
/* Kick the tasklet for some interrupt coalescing and reset handling */
1431+
tasklet_hi_schedule(&engine->execlists.tasklet);
1432+
}
1433+
13201434
static void queue_request(struct intel_engine_cs *engine,
13211435
struct i915_sched_node *node,
13221436
int prio)
@@ -2542,6 +2656,7 @@ static int gen8_init_rcs_context(struct i915_request *rq)
25422656

25432657
static void execlists_park(struct intel_engine_cs *engine)
25442658
{
2659+
del_timer_sync(&engine->execlists.timer);
25452660
intel_engine_park(engine);
25462661
}
25472662

@@ -2639,6 +2754,7 @@ int intel_execlists_submission_setup(struct intel_engine_cs *engine)
26392754

26402755
tasklet_init(&engine->execlists.tasklet,
26412756
execlists_submission_tasklet, (unsigned long)engine);
2757+
timer_setup(&engine->execlists.timer, execlists_submission_timer, 0);
26422758

26432759
logical_ring_default_vfuncs(engine);
26442760
logical_ring_default_irqs(engine);

0 commit comments

Comments
 (0)