]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - drivers/gpu/drm/i915/intel_lrc.c
drm/i915: Split execlist priority queue into rbtree + linked list
[mirror_ubuntu-artful-kernel.git] / drivers / gpu / drm / i915 / intel_lrc.c
index 53ec0d5713adc377f1360ccae27ab0252cfc9df4..626db6185a21e913025d59a7f8a925c627d3b750 100644 (file)
@@ -436,57 +436,75 @@ static void execlists_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 *cursor =
-                       rb_entry(rb, typeof(*cursor), priotree.node);
-
-               /* 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
-                * updating the RING_TAIL to point to the end of the second
-                * request, and so we never need to tell the hardware about
-                * the first.
-                */
-               if (last && !can_merge_ctx(cursor->ctx, last->ctx)) {
-                       /* If we are on the second port and cannot combine
-                        * this request with the last, then we are done.
-                        */
-                       if (port != engine->execlist_port)
-                               break;
-
-                       /* If GVT overrides us we only ever submit port[0],
-                        * leaving port[1] empty. Note that we also have
-                        * to be careful that we don't queue the same
-                        * context (even though a different request) to
-                        * the second 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) {
+                       /*
+                        * 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 updating the RING_TAIL to point to the end of the
+                        * second request, and so we never need to tell the
+                        * hardware about the first.
                         */
-                       if (ctx_single_port_submission(last->ctx) ||
-                           ctx_single_port_submission(cursor->ctx))
-                               break;
+                       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 (port != engine->execlist_port) {
+                                       __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 to be careful that we don't queue
+                                * the same context (even though a different
+                                * request) to the second port.
+                                */
+                               if (ctx_single_port_submission(last->ctx) ||
+                                   ctx_single_port_submission(rq->ctx)) {
+                                       __list_del_many(&p->requests,
+                                                       &rq->priotree.link);
+                                       goto done;
+                               }
+
+                               GEM_BUG_ON(last->ctx == rq->ctx);
+
+                               if (submit)
+                                       port_assign(port, last);
+                               port++;
+                       }
 
-                       GEM_BUG_ON(last->ctx == cursor->ctx);
+                       INIT_LIST_HEAD(&rq->priotree.link);
+                       rq->priotree.priority = INT_MAX;
 
-                       if (submit)
-                               port_assign(port, last);
-                       port++;
+                       __i915_gem_request_submit(rq);
+                       trace_i915_gem_request_in(rq, port_index(port, engine));
+                       last = rq;
+                       submit = true;
                }
 
                rb = rb_next(rb);
-               rb_erase(&cursor->priotree.node, &engine->execlist_queue);
-               RB_CLEAR_NODE(&cursor->priotree.node);
-               cursor->priotree.priority = INT_MAX;
-
-               __i915_gem_request_submit(cursor);
-               trace_i915_gem_request_in(cursor, port_index(port, engine));
-               last = cursor;
-               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);
 
        if (submit)
@@ -610,28 +628,66 @@ static void intel_lrc_irq_handler(unsigned long data)
        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;
 
+       if (unlikely(engine->no_priolist))
+               prio = I915_PRIORITY_NORMAL;
+
+find_priolist:
        /* most positive priority is scheduled first, equal priorities fifo */
        rb = NULL;
-       p = &root->rb_node;
-       while (*p) {
-               struct i915_priotree *pos;
-
-               rb = *p;
-               pos = rb_entry(rb, typeof(*pos), node);
-               if (pt->priority > pos->priority) {
-                       p = &rb->rb_left;
-               } else {
-                       p = &rb->rb_right;
+       parent = &engine->execlist_queue.rb_node;
+       while (*parent) {
+               rb = *parent;
+               p = rb_entry(rb, typeof(*p), node);
+               if (prio > p->priority) {
+                       parent = &rb->rb_left;
+               } else if (prio < p->priority) {
+                       parent = &rb->rb_right;
                        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;
 }
@@ -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. */
        spin_lock_irqsave(&engine->timeline->lock, flags);
 
-       if (insert_request(&request->priotree, &engine->execlist_queue)) {
-               engine->execlist_first = &request->priotree.node;
+       if (insert_request(engine,
+                          &request->priotree,
+                          request->priotree.priority)) {
                if (execlists_elsp_ready(engine))
                        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);
 }
 
@@ -734,10 +794,9 @@ static void execlists_schedule(struct drm_i915_gem_request *request, int prio)
                        continue;
 
                pt->priority = prio;
-               if (!RB_EMPTY_NODE(&pt->node)) {
-                       rb_erase(&pt->node, &engine->execlist_queue);
-                       if (insert_request(pt, &engine->execlist_queue))
-                               engine->execlist_first = &pt->node;
+               if (!list_empty(&pt->link)) {
+                       __list_del_entry(&pt->link);
+                       insert_request(engine, pt, prio);
                }
        }