Commit 6c067579 authored by Chris Wilson's avatar Chris Wilson

drm/i915: Split execlist priority queue into rbtree + linked list

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: default avatarMichał Wajdeczko <michal.wajdeczko@intel.com>
Signed-off-by: default avatarChris 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: default avatarMichał Winiarski <michal.winiarski@intel.com>
Reviewed-by: default avatarTvrtko Ursulin <tvrtko.ursulin@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20170517121007.27224-8-chris@chris-wilson.co.uk
parent e4f815f6
...@@ -3352,7 +3352,6 @@ static int i915_engine_info(struct seq_file *m, void *unused) ...@@ -3352,7 +3352,6 @@ static int i915_engine_info(struct seq_file *m, void *unused)
if (i915.enable_execlists) { if (i915.enable_execlists) {
u32 ptr, read, write; u32 ptr, read, write;
struct rb_node *rb;
unsigned int idx; unsigned int idx;
seq_printf(m, "\tExeclist status: 0x%08x %08x\n", seq_printf(m, "\tExeclist status: 0x%08x %08x\n",
...@@ -3396,8 +3395,12 @@ static int i915_engine_info(struct seq_file *m, void *unused) ...@@ -3396,8 +3395,12 @@ static int i915_engine_info(struct seq_file *m, void *unused)
rcu_read_unlock(); rcu_read_unlock();
spin_lock_irq(&engine->timeline->lock); spin_lock_irq(&engine->timeline->lock);
for (rb = engine->execlist_first; rb; rb = rb_next(rb)) { for (rb = engine->execlist_first; rb; rb = rb_next(rb)){
rq = rb_entry(rb, typeof(*rq), priotree.node); 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 "); print_request(m, rq, "\t\tQ ");
} }
spin_unlock_irq(&engine->timeline->lock); spin_unlock_irq(&engine->timeline->lock);
......
...@@ -3155,8 +3155,6 @@ i915_gem_idle_work_handler(struct work_struct *work) ...@@ -3155,8 +3155,6 @@ i915_gem_idle_work_handler(struct work_struct *work)
struct drm_i915_private *dev_priv = struct drm_i915_private *dev_priv =
container_of(work, typeof(*dev_priv), gt.idle_work.work); container_of(work, typeof(*dev_priv), gt.idle_work.work);
struct drm_device *dev = &dev_priv->drm; struct drm_device *dev = &dev_priv->drm;
struct intel_engine_cs *engine;
enum intel_engine_id id;
bool rearm_hangcheck; bool rearm_hangcheck;
if (!READ_ONCE(dev_priv->gt.awake)) if (!READ_ONCE(dev_priv->gt.awake))
...@@ -3194,10 +3192,7 @@ i915_gem_idle_work_handler(struct work_struct *work) ...@@ -3194,10 +3192,7 @@ i915_gem_idle_work_handler(struct work_struct *work)
if (wait_for(intel_engines_are_idle(dev_priv), 10)) if (wait_for(intel_engines_are_idle(dev_priv), 10))
DRM_ERROR("Timeout waiting for engines to idle\n"); DRM_ERROR("Timeout waiting for engines to idle\n");
for_each_engine(engine, dev_priv, id) { intel_engines_mark_idle(dev_priv);
intel_engine_disarm_breadcrumbs(engine);
i915_gem_batch_pool_fini(&engine->batch_pool);
}
i915_gem_timelines_mark_idle(dev_priv); i915_gem_timelines_mark_idle(dev_priv);
GEM_BUG_ON(!dev_priv->gt.awake); GEM_BUG_ON(!dev_priv->gt.awake);
......
...@@ -159,7 +159,7 @@ i915_priotree_fini(struct drm_i915_private *i915, struct i915_priotree *pt) ...@@ -159,7 +159,7 @@ i915_priotree_fini(struct drm_i915_private *i915, struct i915_priotree *pt)
{ {
struct i915_dependency *dep, *next; 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) /* Everyone we depended upon (the fences we wait to be signaled)
* should retire before us and remove themselves from our list. * should retire before us and remove themselves from our list.
...@@ -185,7 +185,7 @@ i915_priotree_init(struct i915_priotree *pt) ...@@ -185,7 +185,7 @@ i915_priotree_init(struct i915_priotree *pt)
{ {
INIT_LIST_HEAD(&pt->signalers_list); INIT_LIST_HEAD(&pt->signalers_list);
INIT_LIST_HEAD(&pt->waiters_list); INIT_LIST_HEAD(&pt->waiters_list);
RB_CLEAR_NODE(&pt->node); INIT_LIST_HEAD(&pt->link);
pt->priority = INT_MIN; pt->priority = INT_MIN;
} }
......
...@@ -67,7 +67,7 @@ struct i915_dependency { ...@@ -67,7 +67,7 @@ struct i915_dependency {
struct i915_priotree { struct i915_priotree {
struct list_head signalers_list; /* those before us, we depend upon */ struct list_head signalers_list; /* those before us, we depend upon */
struct list_head waiters_list; /* those after us, they depend upon us */ struct list_head waiters_list; /* those after us, they depend upon us */
struct rb_node node; struct list_head link;
int priority; int priority;
#define I915_PRIORITY_MAX 1024 #define I915_PRIORITY_MAX 1024
#define I915_PRIORITY_NORMAL 0 #define I915_PRIORITY_NORMAL 0
......
...@@ -674,21 +674,24 @@ static bool i915_guc_dequeue(struct intel_engine_cs *engine) ...@@ -674,21 +674,24 @@ static bool i915_guc_dequeue(struct intel_engine_cs *engine)
spin_lock_irq(&engine->timeline->lock); spin_lock_irq(&engine->timeline->lock);
rb = engine->execlist_first; rb = engine->execlist_first;
GEM_BUG_ON(rb_first(&engine->execlist_queue) != rb);
while (rb) { while (rb) {
struct drm_i915_gem_request *rq = struct i915_priolist *p = rb_entry(rb, typeof(*p), node);
rb_entry(rb, typeof(*rq), priotree.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 (last && rq->ctx != last->ctx) {
if (port != engine->execlist_port) if (port != engine->execlist_port) {
break; __list_del_many(&p->requests,
&rq->priotree.link);
goto done;
}
port_assign(port, last); port_assign(port, last);
port++; port++;
} }
rb = rb_next(rb); INIT_LIST_HEAD(&rq->priotree.link);
rb_erase(&rq->priotree.node, &engine->execlist_queue);
RB_CLEAR_NODE(&rq->priotree.node);
rq->priotree.priority = INT_MAX; rq->priotree.priority = INT_MAX;
i915_guc_submit(rq); i915_guc_submit(rq);
...@@ -696,10 +699,17 @@ static bool i915_guc_dequeue(struct intel_engine_cs *engine) ...@@ -696,10 +699,17 @@ static bool i915_guc_dequeue(struct intel_engine_cs *engine)
last = rq; last = rq;
submit = true; submit = true;
} }
if (submit) {
port_assign(port, last); rb = rb_next(rb);
engine->execlist_first = rb; rb_erase(&p->node, &engine->execlist_queue);
INIT_LIST_HEAD(&p->requests);
if (p->priority != I915_PRIORITY_NORMAL)
kfree(p);
} }
done:
engine->execlist_first = rb;
if (submit)
port_assign(port, last);
spin_unlock_irq(&engine->timeline->lock); spin_unlock_irq(&engine->timeline->lock);
return submit; return submit;
......
...@@ -105,4 +105,13 @@ ...@@ -105,4 +105,13 @@
__idx; \ __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 */ #endif /* !__I915_UTILS_H */
...@@ -1274,6 +1274,18 @@ void intel_engines_reset_default_submission(struct drm_i915_private *i915) ...@@ -1274,6 +1274,18 @@ void intel_engines_reset_default_submission(struct drm_i915_private *i915)
engine->set_default_submission(engine); 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) #if IS_ENABLED(CONFIG_DRM_I915_SELFTEST)
#include "selftests/mock_engine.c" #include "selftests/mock_engine.c"
#endif #endif
...@@ -436,57 +436,75 @@ static void execlists_dequeue(struct intel_engine_cs *engine) ...@@ -436,57 +436,75 @@ static void execlists_dequeue(struct intel_engine_cs *engine)
spin_lock_irq(&engine->timeline->lock); spin_lock_irq(&engine->timeline->lock);
rb = engine->execlist_first; rb = engine->execlist_first;
GEM_BUG_ON(rb_first(&engine->execlist_queue) != rb);
while (rb) { while (rb) {
struct drm_i915_gem_request *cursor = struct i915_priolist *p = rb_entry(rb, typeof(*p), node);
rb_entry(rb, typeof(*cursor), priotree.node); struct drm_i915_gem_request *rq, *rn;
/* Can we combine this request with the current port? It has to list_for_each_entry_safe(rq, rn, &p->requests, priotree.link) {
* be the same context/ringbuffer and not have any exceptions /*
* (e.g. GVT saying never to combine contexts). * Can we combine this request with the current port?
* It has to be the same context/ringbuffer and not
* have any exceptions (e.g. GVT saying never to
* combine contexts).
* *
* If we can combine the requests, we can execute both by * If we can combine the requests, we can execute both
* updating the RING_TAIL to point to the end of the second * by updating the RING_TAIL to point to the end of the
* request, and so we never need to tell the hardware about * second request, and so we never need to tell the
* the first. * hardware about the first.
*/ */
if (last && !can_merge_ctx(cursor->ctx, last->ctx)) { if (last && !can_merge_ctx(rq->ctx, last->ctx)) {
/* If we are on the second port and cannot combine /*
* this request with the last, then we are done. * If we are on the second port and cannot
* combine this request with the last, then we
* are done.
*/ */
if (port != engine->execlist_port) if (port != engine->execlist_port) {
break; __list_del_many(&p->requests,
&rq->priotree.link);
goto done;
}
/* If GVT overrides us we only ever submit port[0], /*
* leaving port[1] empty. Note that we also have * If GVT overrides us we only ever submit
* to be careful that we don't queue the same * port[0], leaving port[1] empty. Note that we
* context (even though a different request) to * also have to be careful that we don't queue
* the second port. * the same context (even though a different
* request) to the second port.
*/ */
if (ctx_single_port_submission(last->ctx) || if (ctx_single_port_submission(last->ctx) ||
ctx_single_port_submission(cursor->ctx)) ctx_single_port_submission(rq->ctx)) {
break; __list_del_many(&p->requests,
&rq->priotree.link);
goto done;
}
GEM_BUG_ON(last->ctx == cursor->ctx); GEM_BUG_ON(last->ctx == rq->ctx);
if (submit) if (submit)
port_assign(port, last); port_assign(port, last);
port++; port++;
} }
rb = rb_next(rb); INIT_LIST_HEAD(&rq->priotree.link);
rb_erase(&cursor->priotree.node, &engine->execlist_queue); rq->priotree.priority = INT_MAX;
RB_CLEAR_NODE(&cursor->priotree.node);
cursor->priotree.priority = INT_MAX;
__i915_gem_request_submit(cursor); __i915_gem_request_submit(rq);
trace_i915_gem_request_in(cursor, port_index(port, engine)); trace_i915_gem_request_in(rq, port_index(port, engine));
last = cursor; last = rq;
submit = true; submit = true;
} }
if (submit) {
port_assign(port, last); rb = rb_next(rb);
engine->execlist_first = rb; rb_erase(&p->node, &engine->execlist_queue);
INIT_LIST_HEAD(&p->requests);
if (p->priority != I915_PRIORITY_NORMAL)
kfree(p);
} }
done:
engine->execlist_first = rb;
if (submit)
port_assign(port, last);
spin_unlock_irq(&engine->timeline->lock); spin_unlock_irq(&engine->timeline->lock);
if (submit) if (submit)
...@@ -610,28 +628,66 @@ static void intel_lrc_irq_handler(unsigned long data) ...@@ -610,28 +628,66 @@ static void intel_lrc_irq_handler(unsigned long data)
intel_uncore_forcewake_put(dev_priv, engine->fw_domains); intel_uncore_forcewake_put(dev_priv, engine->fw_domains);
} }
static bool insert_request(struct i915_priotree *pt, struct rb_root *root) static bool
insert_request(struct intel_engine_cs *engine,
struct i915_priotree *pt,
int prio)
{ {
struct rb_node **p, *rb; struct i915_priolist *p;
struct rb_node **parent, *rb;
bool first = true; bool first = true;
if (unlikely(engine->no_priolist))
prio = I915_PRIORITY_NORMAL;
find_priolist:
/* most positive priority is scheduled first, equal priorities fifo */ /* most positive priority is scheduled first, equal priorities fifo */
rb = NULL; rb = NULL;
p = &root->rb_node; parent = &engine->execlist_queue.rb_node;
while (*p) { while (*parent) {
struct i915_priotree *pos; rb = *parent;
p = rb_entry(rb, typeof(*p), node);
rb = *p; if (prio > p->priority) {
pos = rb_entry(rb, typeof(*pos), node); parent = &rb->rb_left;
if (pt->priority > pos->priority) { } else if (prio < p->priority) {
p = &rb->rb_left; parent = &rb->rb_right;
} else {
p = &rb->rb_right;
first = false; first = false;
} else {
list_add_tail(&pt->link, &p->requests);
return false;
}
}
if (prio == I915_PRIORITY_NORMAL) {
p = &engine->default_priolist;
} else {
p = kmalloc(sizeof(*p), GFP_ATOMIC);
/* Convert an allocation failure to a priority bump */
if (unlikely(!p)) {
prio = I915_PRIORITY_NORMAL; /* recurses just once */
/* To maintain ordering with all rendering, after an
* allocation failure we have to disable all scheduling.
* Requests will then be executed in fifo, and schedule
* will ensure that dependencies are emitted in fifo.
* There will be still some reordering with existing
* requests, so if userspace lied about their
* dependencies that reordering may be visible.
*/
engine->no_priolist = true;
goto find_priolist;
} }
} }
rb_link_node(&pt->node, rb, p);
rb_insert_color(&pt->node, root); p->priority = prio;
rb_link_node(&p->node, rb, parent);
rb_insert_color(&p->node, &engine->execlist_queue);
INIT_LIST_HEAD(&p->requests);
list_add_tail(&pt->link, &p->requests);
if (first)
engine->execlist_first = &p->node;
return first; return first;
} }
...@@ -644,12 +700,16 @@ static void execlists_submit_request(struct drm_i915_gem_request *request) ...@@ -644,12 +700,16 @@ static void execlists_submit_request(struct drm_i915_gem_request *request)
/* Will be called from irq-context when using foreign fences. */ /* Will be called from irq-context when using foreign fences. */
spin_lock_irqsave(&engine->timeline->lock, flags); spin_lock_irqsave(&engine->timeline->lock, flags);
if (insert_request(&request->priotree, &engine->execlist_queue)) { if (insert_request(engine,
engine->execlist_first = &request->priotree.node; &request->priotree,
request->priotree.priority)) {
if (execlists_elsp_ready(engine)) if (execlists_elsp_ready(engine))
tasklet_hi_schedule(&engine->irq_tasklet); tasklet_hi_schedule(&engine->irq_tasklet);
} }
GEM_BUG_ON(!engine->execlist_first);
GEM_BUG_ON(list_empty(&request->priotree.link));
spin_unlock_irqrestore(&engine->timeline->lock, flags); spin_unlock_irqrestore(&engine->timeline->lock, flags);
} }
...@@ -734,10 +794,9 @@ static void execlists_schedule(struct drm_i915_gem_request *request, int prio) ...@@ -734,10 +794,9 @@ static void execlists_schedule(struct drm_i915_gem_request *request, int prio)
continue; continue;
pt->priority = prio; pt->priority = prio;
if (!RB_EMPTY_NODE(&pt->node)) { if (!list_empty(&pt->link)) {
rb_erase(&pt->node, &engine->execlist_queue); __list_del_entry(&pt->link);
if (insert_request(pt, &engine->execlist_queue)) insert_request(engine, pt, prio);
engine->execlist_first = &pt->node;
} }
} }
......
...@@ -177,6 +177,12 @@ enum intel_engine_id { ...@@ -177,6 +177,12 @@ enum intel_engine_id {
VECS VECS
}; };
struct i915_priolist {
struct rb_node node;
struct list_head requests;
int priority;
};
#define INTEL_ENGINE_CS_MAX_NAME 8 #define INTEL_ENGINE_CS_MAX_NAME 8
struct intel_engine_cs { struct intel_engine_cs {
...@@ -367,6 +373,8 @@ struct intel_engine_cs { ...@@ -367,6 +373,8 @@ struct intel_engine_cs {
/* Execlists */ /* Execlists */
struct tasklet_struct irq_tasklet; struct tasklet_struct irq_tasklet;
struct i915_priolist default_priolist;
bool no_priolist;
struct execlist_port { struct execlist_port {
struct drm_i915_gem_request *request_count; struct drm_i915_gem_request *request_count;
#define EXECLIST_COUNT_BITS 2 #define EXECLIST_COUNT_BITS 2
...@@ -723,6 +731,7 @@ static inline u32 *gen8_emit_pipe_control(u32 *batch, u32 flags, u32 offset) ...@@ -723,6 +731,7 @@ static inline u32 *gen8_emit_pipe_control(u32 *batch, u32 flags, u32 offset)
bool intel_engine_is_idle(struct intel_engine_cs *engine); bool intel_engine_is_idle(struct intel_engine_cs *engine);
bool intel_engines_are_idle(struct drm_i915_private *dev_priv); bool intel_engines_are_idle(struct drm_i915_private *dev_priv);
void intel_engines_mark_idle(struct drm_i915_private *i915);
void intel_engines_reset_default_submission(struct drm_i915_private *i915); void intel_engines_reset_default_submission(struct drm_i915_private *i915);
#endif /* _INTEL_RINGBUFFER_H_ */ #endif /* _INTEL_RINGBUFFER_H_ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment