Skip to content

Commit

Permalink
drm/i915: Split execlist priority queue into rbtree + linked list
Browse files Browse the repository at this point in the history
All the requests at the same priority are executed in FIFO order. They
do not need to be stored in the rbtree themselves, as they are a simple
list within a level. If we move the requests at one priority into a list,
we can then reduce the rbtree to the set of priorities. This should keep
the height of the rbtree small, as the number of active priorities can not
exceed the number of active requests and should be typically only a few.

Currently, we have ~2k possible different priority levels, that may
increase to allow even more fine grained selection. Allocating those in
advance seems a waste (and may be impossible), so we opt for allocating
upon first use, and freeing after its requests are depleted. To avoid
the possibility of an allocation failure causing us to lose a request,
we preallocate the default priority (0) and bump any request to that
priority if we fail to allocate it the appropriate plist. Having a
request (that is ready to run, so not leading to corruption) execute
out-of-order is better than leaking the request (and its dependency
tree) entirely.

There should be a benefit to reducing execlists_dequeue() to principally
using a simple list (and reducing the frequency of both rbtree iteration
and balancing on erase) but for typical workloads, request coalescing
should be small enough that we don't notice any change. The main gain is
from improving PI calls to schedule, and the explicit list within a
level should make request unwinding simpler (we just need to insert at
the head of the list rather than the tail and not have to make the
rbtree search more complicated).

v2: Avoid use-after-free when deleting a depleted priolist

v3: Michał found the solution to handling the allocation failure
gracefully. If we disable all priority scheduling following the
allocation failure, those requests will be executed in fifo and we will
ensure that this request and its dependencies are in strict fifo (even
when it doesn't realise it is only a single list). Normal scheduling is
restored once we know the device is idle, until the next failure!
Suggested-by: Michał Wajdeczko <michal.wajdeczko@intel.com>

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Cc: Michał Winiarski <michal.winiarski@intel.com>
Cc: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com>
Reviewed-by: Michał Winiarski <michal.winiarski@intel.com>
Reviewed-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20170517121007.27224-8-chris@chris-wilson.co.uk
  • Loading branch information
Chris Wilson committed May 17, 2017
1 parent e4f815f commit 6c06757
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 95 deletions.
11 changes: 7 additions & 4 deletions drivers/gpu/drm/i915/i915_debugfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -3352,7 +3352,6 @@ static int i915_engine_info(struct seq_file *m, void *unused)

if (i915.enable_execlists) {
u32 ptr, read, write;
struct rb_node *rb;
unsigned int idx;

seq_printf(m, "\tExeclist status: 0x%08x %08x\n",
Expand Down Expand Up @@ -3396,9 +3395,13 @@ static int i915_engine_info(struct seq_file *m, void *unused)
rcu_read_unlock();

spin_lock_irq(&engine->timeline->lock);
for (rb = engine->execlist_first; rb; rb = rb_next(rb)) {
rq = rb_entry(rb, typeof(*rq), priotree.node);
print_request(m, rq, "\t\tQ ");
for (rb = engine->execlist_first; rb; rb = rb_next(rb)){
struct i915_priolist *p =
rb_entry(rb, typeof(*p), node);

list_for_each_entry(rq, &p->requests,
priotree.link)
print_request(m, rq, "\t\tQ ");
}
spin_unlock_irq(&engine->timeline->lock);
} else if (INTEL_GEN(dev_priv) > 6) {
Expand Down
7 changes: 1 addition & 6 deletions drivers/gpu/drm/i915/i915_gem.c
Original file line number Diff line number Diff line change
Expand Up @@ -3155,8 +3155,6 @@ i915_gem_idle_work_handler(struct work_struct *work)
struct drm_i915_private *dev_priv =
container_of(work, typeof(*dev_priv), gt.idle_work.work);
struct drm_device *dev = &dev_priv->drm;
struct intel_engine_cs *engine;
enum intel_engine_id id;
bool rearm_hangcheck;

if (!READ_ONCE(dev_priv->gt.awake))
Expand Down Expand Up @@ -3194,10 +3192,7 @@ i915_gem_idle_work_handler(struct work_struct *work)
if (wait_for(intel_engines_are_idle(dev_priv), 10))
DRM_ERROR("Timeout waiting for engines to idle\n");

for_each_engine(engine, dev_priv, id) {
intel_engine_disarm_breadcrumbs(engine);
i915_gem_batch_pool_fini(&engine->batch_pool);
}
intel_engines_mark_idle(dev_priv);
i915_gem_timelines_mark_idle(dev_priv);

GEM_BUG_ON(!dev_priv->gt.awake);
Expand Down
4 changes: 2 additions & 2 deletions drivers/gpu/drm/i915/i915_gem_request.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ i915_priotree_fini(struct drm_i915_private *i915, struct i915_priotree *pt)
{
struct i915_dependency *dep, *next;

GEM_BUG_ON(!RB_EMPTY_NODE(&pt->node));
GEM_BUG_ON(!list_empty(&pt->link));

/* Everyone we depended upon (the fences we wait to be signaled)
* should retire before us and remove themselves from our list.
Expand All @@ -185,7 +185,7 @@ i915_priotree_init(struct i915_priotree *pt)
{
INIT_LIST_HEAD(&pt->signalers_list);
INIT_LIST_HEAD(&pt->waiters_list);
RB_CLEAR_NODE(&pt->node);
INIT_LIST_HEAD(&pt->link);
pt->priority = INT_MIN;
}

Expand Down
2 changes: 1 addition & 1 deletion drivers/gpu/drm/i915/i915_gem_request.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ struct i915_dependency {
struct i915_priotree {
struct list_head signalers_list; /* those before us, we depend upon */
struct list_head waiters_list; /* those after us, they depend upon us */
struct rb_node node;
struct list_head link;
int priority;
#define I915_PRIORITY_MAX 1024
#define I915_PRIORITY_NORMAL 0
Expand Down
50 changes: 30 additions & 20 deletions drivers/gpu/drm/i915/i915_guc_submission.c
Original file line number Diff line number Diff line change
Expand Up @@ -674,32 +674,42 @@ static bool i915_guc_dequeue(struct intel_engine_cs *engine)

spin_lock_irq(&engine->timeline->lock);
rb = engine->execlist_first;
GEM_BUG_ON(rb_first(&engine->execlist_queue) != rb);
while (rb) {
struct drm_i915_gem_request *rq =
rb_entry(rb, typeof(*rq), priotree.node);

if (last && rq->ctx != last->ctx) {
if (port != engine->execlist_port)
break;

port_assign(port, last);
port++;
struct i915_priolist *p = rb_entry(rb, typeof(*p), node);
struct drm_i915_gem_request *rq, *rn;

list_for_each_entry_safe(rq, rn, &p->requests, priotree.link) {
if (last && rq->ctx != last->ctx) {
if (port != engine->execlist_port) {
__list_del_many(&p->requests,
&rq->priotree.link);
goto done;
}

port_assign(port, last);
port++;
}

INIT_LIST_HEAD(&rq->priotree.link);
rq->priotree.priority = INT_MAX;

i915_guc_submit(rq);
trace_i915_gem_request_in(rq, port_index(port, engine));
last = rq;
submit = true;
}

rb = rb_next(rb);
rb_erase(&rq->priotree.node, &engine->execlist_queue);
RB_CLEAR_NODE(&rq->priotree.node);
rq->priotree.priority = INT_MAX;

i915_guc_submit(rq);
trace_i915_gem_request_in(rq, port_index(port, engine));
last = rq;
submit = true;
rb_erase(&p->node, &engine->execlist_queue);
INIT_LIST_HEAD(&p->requests);
if (p->priority != I915_PRIORITY_NORMAL)
kfree(p);
}
if (submit) {
done:
engine->execlist_first = rb;
if (submit)
port_assign(port, last);
engine->execlist_first = rb;
}
spin_unlock_irq(&engine->timeline->lock);

return submit;
Expand Down
9 changes: 9 additions & 0 deletions drivers/gpu/drm/i915/i915_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,13 @@
__idx; \
})

#include <linux/list.h>

static inline void __list_del_many(struct list_head *head,
struct list_head *first)
{
first->prev = head;
WRITE_ONCE(head->next, first);
}

#endif /* !__I915_UTILS_H */
12 changes: 12 additions & 0 deletions drivers/gpu/drm/i915/intel_engine_cs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,18 @@ void intel_engines_reset_default_submission(struct drm_i915_private *i915)
engine->set_default_submission(engine);
}

void intel_engines_mark_idle(struct drm_i915_private *i915)
{
struct intel_engine_cs *engine;
enum intel_engine_id id;

for_each_engine(engine, i915, id) {
intel_engine_disarm_breadcrumbs(engine);
i915_gem_batch_pool_fini(&engine->batch_pool);
engine->no_priolist = false;
}
}

#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST)
#include "selftests/mock_engine.c"
#endif
Loading

0 comments on commit 6c06757

Please sign in to comment.