summaryrefslogtreecommitdiff
path: root/drivers/gpu
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu')
-rw-r--r--drivers/gpu/drm/i915/Makefile1
-rw-r--r--drivers/gpu/drm/i915/i915_dma.c3
-rw-r--r--drivers/gpu/drm/i915/i915_drv.h6
-rw-r--r--drivers/gpu/drm/i915/i915_gem.c5
-rw-r--r--drivers/gpu/drm/i915/i915_scheduler.c863
-rw-r--r--drivers/gpu/drm/i915/i915_scheduler.h113
6 files changed, 991 insertions, 0 deletions
diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile
index 95ad56b6aab3..d6d5afbcf8d1 100644
--- a/drivers/gpu/drm/i915/Makefile
+++ b/drivers/gpu/drm/i915/Makefile
@@ -10,6 +10,7 @@
i915-y := i915_drv.o \
i915_irq.o \
i915_params.o \
+ i915_scheduler.o \
i915_suspend.o \
i915_sysfs.o \
intel_csr.o \
diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c
index fc8ac98c12d7..63b32763b438 100644
--- a/drivers/gpu/drm/i915/i915_dma.c
+++ b/drivers/gpu/drm/i915/i915_dma.c
@@ -37,6 +37,7 @@
#include "i915_drv.h"
#include "i915_vgpu.h"
#include "i915_trace.h"
+#include "i915_scheduler.h"
#include <linux/pci.h>
#include <linux/console.h>
#include <linux/vt.h>
@@ -1445,6 +1446,8 @@ int i915_driver_unload(struct drm_device *dev)
intel_csr_ucode_fini(dev_priv);
+ i915_scheduler_destroy(dev_priv);
+
/* Free error state after interrupts are fully disabled. */
cancel_delayed_work_sync(&dev_priv->gpu_error.hangcheck_work);
i915_destroy_error_state(dev);
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index 8d7d796a73be..29dbed927a37 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -1701,6 +1701,8 @@ struct i915_execbuffer_params {
struct drm_i915_gem_request *request;
};
+struct i915_scheduler;
+
/* used in computing the new watermarks state */
struct intel_wm_config {
unsigned int num_pipes_active;
@@ -1969,6 +1971,8 @@ struct drm_i915_private {
struct i915_runtime_pm pm;
+ struct i915_scheduler *scheduler;
+
/* Abstract the submission mechanism (legacy ringbuffer or execlists) away */
struct {
int (*execbuf_submit)(struct i915_execbuffer_params *params,
@@ -2301,6 +2305,8 @@ struct drm_i915_gem_request {
/** process identifier submitting this request */
struct pid *pid;
+ struct i915_scheduler_queue_entry *scheduler_qe;
+
/**
* The ELSP only accepts two elements at a time, so we queue
* context/tail pairs on a given queue (ring->execlist_queue) until the
diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c
index f8c69ec6de63..66d9c8d9a81c 100644
--- a/drivers/gpu/drm/i915/i915_gem.c
+++ b/drivers/gpu/drm/i915/i915_gem.c
@@ -32,6 +32,7 @@
#include "i915_vgpu.h"
#include "i915_trace.h"
#include "intel_drv.h"
+#include "i915_scheduler.h"
#include <linux/shmem_fs.h>
#include <linux/slab.h>
#include <linux/swap.h>
@@ -5334,6 +5335,10 @@ int i915_gem_init(struct drm_device *dev)
*/
intel_uncore_forcewake_get(dev_priv, FORCEWAKE_ALL);
+ ret = i915_scheduler_init(dev);
+ if (ret)
+ goto out_unlock;
+
ret = i915_gem_init_userptr(dev);
if (ret)
goto out_unlock;
diff --git a/drivers/gpu/drm/i915/i915_scheduler.c b/drivers/gpu/drm/i915/i915_scheduler.c
new file mode 100644
index 000000000000..af2bd1691378
--- /dev/null
+++ b/drivers/gpu/drm/i915/i915_scheduler.c
@@ -0,0 +1,863 @@
+/*
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "i915_drv.h"
+#include "intel_drv.h"
+#include "i915_scheduler.h"
+
+#define for_each_scheduler_node(node, id) \
+ list_for_each_entry((node), &scheduler->node_queue[(id)], link)
+
+#define assert_scheduler_lock_held(scheduler) \
+ do { \
+ WARN_ONCE(!spin_is_locked(&(scheduler)->lock), "Spinlock not locked!"); \
+ } while(0)
+
+/**
+ * i915_scheduler_is_enabled - Returns true if the scheduler is enabled.
+ * @dev: DRM device
+ */
+bool i915_scheduler_is_enabled(struct drm_device *dev)
+{
+ struct drm_i915_private *dev_priv = to_i915(dev);
+
+ return dev_priv->scheduler != NULL;
+}
+
+/**
+ * i915_scheduler_init - Initialise the scheduler.
+ * @dev: DRM device
+ * Returns zero on success or -ENOMEM if memory allocations fail.
+ */
+int i915_scheduler_init(struct drm_device *dev)
+{
+ struct drm_i915_private *dev_priv = to_i915(dev);
+ struct i915_scheduler *scheduler = dev_priv->scheduler;
+ int e;
+
+ if (scheduler)
+ return 0;
+
+ scheduler = kzalloc(sizeof(*scheduler), GFP_KERNEL);
+ if (!scheduler)
+ return -ENOMEM;
+
+ spin_lock_init(&scheduler->lock);
+
+ for (e = 0; e < I915_NUM_ENGINES; e++) {
+ INIT_LIST_HEAD(&scheduler->node_queue[e]);
+ scheduler->counts[e].flying = 0;
+ scheduler->counts[e].queued = 0;
+ }
+
+ /* Default tuning values: */
+ scheduler->priority_level_min = -1023;
+ scheduler->priority_level_max = 1023;
+ scheduler->priority_level_bump = 50;
+ scheduler->priority_level_preempt = 900;
+ scheduler->min_flying = 2;
+
+ dev_priv->scheduler = scheduler;
+
+ return 0;
+}
+
+/**
+ * i915_scheduler_destroy - Get rid of the scheduler.
+ * @dev: DRM device
+ */
+void i915_scheduler_destroy(struct drm_i915_private *dev_priv)
+{
+ struct i915_scheduler *scheduler = dev_priv->scheduler;
+ int e;
+
+ if (!scheduler)
+ return;
+
+ for (e = 0; e < I915_NUM_ENGINES; e++)
+ WARN(!list_empty(&scheduler->node_queue[e]), "Destroying with list entries on engine %d!", e);
+
+ kfree(scheduler);
+ dev_priv->scheduler = NULL;
+}
+
+/*
+ * Add a popped node back in to the queue. For example, because the engine
+ * was hung when execfinal() was called and thus the engine submission needs
+ * to be retried later.
+ */
+static void i915_scheduler_node_requeue(struct i915_scheduler *scheduler,
+ struct i915_scheduler_queue_entry *node)
+{
+ WARN_ON(!I915_SQS_IS_FLYING(node));
+
+ /* Seqno will be reassigned on relaunch */
+ node->params.request->seqno = 0;
+ node->status = I915_SQS_QUEUED;
+ scheduler->counts[node->params.engine->id].flying--;
+ scheduler->counts[node->params.engine->id].queued++;
+}
+
+/*
+ * Give up on a node completely. For example, because it is causing the
+ * engine to hang or is using some resource that no longer exists.
+ */
+static void i915_scheduler_node_kill(struct i915_scheduler *scheduler,
+ struct i915_scheduler_queue_entry *node)
+{
+ WARN_ON(I915_SQS_IS_COMPLETE(node));
+
+ if (I915_SQS_IS_FLYING(node))
+ scheduler->counts[node->params.engine->id].flying--;
+ else
+ scheduler->counts[node->params.engine->id].queued--;
+
+ node->status = I915_SQS_DEAD;
+}
+
+/* Mark a node as in flight on the hardware. */
+static void i915_scheduler_node_fly(struct i915_scheduler_queue_entry *node)
+{
+ struct drm_i915_private *dev_priv = to_i915(node->params.dev);
+ struct i915_scheduler *scheduler = dev_priv->scheduler;
+ struct intel_engine_cs *engine = node->params.engine;
+
+ assert_scheduler_lock_held(scheduler);
+
+ WARN_ON(node->status != I915_SQS_POPPED);
+
+ /*
+ * Add the node (which should currently be in state popped) to the
+ * front of the queue. This ensure that flying nodes are always held
+ * in hardware submission order.
+ */
+ list_add(&node->link, &scheduler->node_queue[engine->id]);
+
+ node->status = I915_SQS_FLYING;
+
+ scheduler->counts[engine->id].flying++;
+
+ if (!(scheduler->flags[engine->id] & I915_SF_INTERRUPTS_ENABLED)) {
+ bool success = true;
+
+ success = engine->irq_get(engine);
+ if (success)
+ scheduler->flags[engine->id] |= I915_SF_INTERRUPTS_ENABLED;
+ }
+}
+
+static inline uint32_t i915_scheduler_count_flying(struct i915_scheduler *scheduler,
+ struct intel_engine_cs *engine)
+{
+ return scheduler->counts[engine->id].flying;
+}
+
+static void i915_scheduler_priority_bump_clear(struct i915_scheduler *scheduler)
+{
+ struct i915_scheduler_queue_entry *node;
+ int i;
+
+ assert_scheduler_lock_held(scheduler);
+
+ /*
+ * Ensure circular dependencies don't cause problems and that a bump
+ * by object usage only bumps each using buffer once:
+ */
+ for (i = 0; i < I915_NUM_ENGINES; i++) {
+ for_each_scheduler_node(node, i)
+ node->bumped = false;
+ }
+}
+
+static int i915_scheduler_priority_bump(struct i915_scheduler *scheduler,
+ struct i915_scheduler_queue_entry *target,
+ uint32_t bump)
+{
+ uint32_t new_priority;
+ int i, count;
+
+ if (target->priority >= scheduler->priority_level_max)
+ return 1;
+
+ if (target->bumped)
+ return 0;
+
+ new_priority = target->priority + bump;
+ if ((new_priority <= target->priority) ||
+ (new_priority > scheduler->priority_level_max))
+ target->priority = scheduler->priority_level_max;
+ else
+ target->priority = new_priority;
+
+ count = 1;
+ target->bumped = true;
+
+ for (i = 0; i < target->num_deps; i++) {
+ if (!target->dep_list[i])
+ continue;
+
+ if (target->dep_list[i]->bumped)
+ continue;
+
+ count += i915_scheduler_priority_bump(scheduler,
+ target->dep_list[i],
+ bump);
+ }
+
+ return count;
+}
+
+/*
+ * Nodes are considered valid dependencies if they are queued on any engine
+ * or if they are in flight on a different engine. In flight on the same
+ * engine is no longer interesting for non-premptive nodes as the engine
+ * serialises execution. For pre-empting nodes, all in flight dependencies
+ * are valid as they must not be jumped by the act of pre-empting.
+ *
+ * Anything that is neither queued nor flying is uninteresting.
+ */
+static inline bool i915_scheduler_is_dependency_valid(
+ struct i915_scheduler_queue_entry *node, uint32_t idx)
+{
+ struct i915_scheduler_queue_entry *dep;
+
+ dep = node->dep_list[idx];
+ if (!dep)
+ return false;
+
+ if (I915_SQS_IS_QUEUED(dep))
+ return true;
+
+ if (I915_SQS_IS_FLYING(dep)) {
+ if (node->params.engine != dep->params.engine)
+ return true;
+ }
+
+ return false;
+}
+
+static int i915_scheduler_pop_from_queue_locked(struct intel_engine_cs *engine,
+ struct i915_scheduler_queue_entry **pop_node)
+{
+ struct drm_i915_private *dev_priv = to_i915(engine->dev);
+ struct i915_scheduler *scheduler = dev_priv->scheduler;
+ struct i915_scheduler_queue_entry *best = NULL;
+ struct i915_scheduler_queue_entry *node;
+ int ret;
+ int i;
+ bool any_queued = false;
+ bool has_local, has_remote, only_remote = false;
+
+ assert_scheduler_lock_held(scheduler);
+
+ *pop_node = NULL;
+ ret = -ENODATA;
+
+ for_each_scheduler_node(node, engine->id) {
+ if (!I915_SQS_IS_QUEUED(node))
+ continue;
+ any_queued = true;
+
+ has_local = false;
+ has_remote = false;
+ for (i = 0; i < node->num_deps; i++) {
+ if (!i915_scheduler_is_dependency_valid(node, i))
+ continue;
+
+ if (node->dep_list[i]->params.engine == node->params.engine)
+ has_local = true;
+ else
+ has_remote = true;
+ }
+
+ if (has_remote && !has_local)
+ only_remote = true;
+
+ if (!has_local && !has_remote) {
+ if (!best ||
+ (node->priority > best->priority))
+ best = node;
+ }
+ }
+
+ if (best) {
+ list_del(&best->link);
+
+ INIT_LIST_HEAD(&best->link);
+ best->status = I915_SQS_POPPED;
+
+ scheduler->counts[engine->id].queued--;
+
+ ret = 0;
+ } else {
+ /* Can only get here if:
+ * (a) there are no buffers in the queue
+ * (b) all queued buffers are dependent on other buffers
+ * e.g. on a buffer that is in flight on a different engine
+ */
+ if (only_remote) {
+ /* The only dependent buffers are on another engine. */
+ ret = -EAGAIN;
+ } else if (any_queued) {
+ /* It seems that something has gone horribly wrong! */
+ WARN_ONCE(true, "Broken dependency tracking on engine %d!\n",
+ (int) engine->id);
+ }
+ }
+
+ *pop_node = best;
+ return ret;
+}
+
+/*
+ * NB: The driver mutex lock must be held before calling this function. It is
+ * only really required during the actual back end submission call. However,
+ * attempting to acquire a mutex while holding a spin lock is a Bad Idea.
+ * And releasing the one before acquiring the other leads to other code
+ * being run and interfering.
+ */
+static int i915_scheduler_submit(struct intel_engine_cs *engine)
+{
+ struct drm_i915_private *dev_priv = to_i915(engine->dev);
+ struct i915_scheduler *scheduler = dev_priv->scheduler;
+ struct i915_scheduler_queue_entry *node;
+ int ret, count = 0, flying;
+
+ WARN_ON(!mutex_is_locked(&engine->dev->struct_mutex));
+
+ spin_lock_irq(&scheduler->lock);
+
+ WARN_ON(scheduler->flags[engine->id] & I915_SF_SUBMITTING);
+ scheduler->flags[engine->id] |= I915_SF_SUBMITTING;
+
+ /* First time around, complain if anything unexpected occurs: */
+ ret = i915_scheduler_pop_from_queue_locked(engine, &node);
+ if (ret)
+ goto error;
+
+ do {
+ WARN_ON(node->params.engine != engine);
+ WARN_ON(node->status != I915_SQS_POPPED);
+ count++;
+
+ /*
+ * The call to pop above will have removed the node from the
+ * list. So add it back in and mark it as in flight.
+ */
+ i915_scheduler_node_fly(node);
+
+ spin_unlock_irq(&scheduler->lock);
+ ret = dev_priv->gt.execbuf_final(&node->params);
+ spin_lock_irq(&scheduler->lock);
+
+ /*
+ * Handle failed submission but first check that the
+ * watchdog/reset code has not nuked the node while we
+ * weren't looking:
+ */
+ if (ret && (node->status != I915_SQS_DEAD)) {
+ bool requeue = true;
+
+ /*
+ * Oh dear! Either the node is broken or the engine is
+ * busy. So need to kill the node or requeue it and try
+ * again later as appropriate.
+ */
+
+ switch (-ret) {
+ case ENODEV:
+ case ENOENT:
+ /* Fatal errors. Kill the node. */
+ requeue = false;
+ i915_scheduler_node_kill(scheduler, node);
+ break;
+
+ case EAGAIN:
+ case EBUSY:
+ case EIO:
+ case ENOMEM:
+ case ERESTARTSYS:
+ case EINTR:
+ /* Supposedly recoverable errors. */
+ break;
+
+ default:
+ /*
+ * Assume the error is recoverable and hope
+ * for the best.
+ */
+ MISSING_CASE(-ret);
+ break;
+ }
+
+ if (requeue) {
+ i915_scheduler_node_requeue(scheduler, node);
+ /*
+ * No point spinning if the engine is currently
+ * unavailable so just give up and come back
+ * later.
+ */
+ break;
+ }
+ }
+
+ /* Keep launching until the sky is sufficiently full. */
+ flying = i915_scheduler_count_flying(scheduler, engine);
+ if (flying >= scheduler->min_flying)
+ break;
+
+ /* Grab another node and go round again... */
+ ret = i915_scheduler_pop_from_queue_locked(engine, &node);
+ } while (ret == 0);
+
+ /* Don't complain about not being able to submit extra entries */
+ if (ret == -ENODATA)
+ ret = 0;
+
+ /*
+ * Bump the priority of everything that was not submitted to prevent
+ * starvation of low priority tasks by a spamming high priority task.
+ */
+ i915_scheduler_priority_bump_clear(scheduler);
+ for_each_scheduler_node(node, engine->id) {
+ if (!I915_SQS_IS_QUEUED(node))
+ continue;
+
+ i915_scheduler_priority_bump(scheduler, node,
+ scheduler->priority_level_bump);
+ }
+
+ /* On success, return the number of buffers submitted. */
+ if (ret == 0)
+ ret = count;
+
+error:
+ scheduler->flags[engine->id] &= ~I915_SF_SUBMITTING;
+ spin_unlock_irq(&scheduler->lock);
+ return ret;
+}
+
+static void i915_generate_dependencies(struct i915_scheduler *scheduler,
+ struct i915_scheduler_queue_entry *node,
+ uint32_t engine)
+{
+ struct i915_scheduler_obj_entry *this, *that;
+ struct i915_scheduler_queue_entry *test;
+ int i, j;
+ bool found;
+
+ for_each_scheduler_node(test, engine) {
+ if (I915_SQS_IS_COMPLETE(test))
+ continue;
+
+ /*
+ * Batches on the same engine for the same
+ * context must be kept in order.
+ */
+ found = (node->params.ctx == test->params.ctx) &&
+ (node->params.engine == test->params.engine);
+
+ /*
+ * Batches working on the same objects must
+ * be kept in order.
+ */
+ for (i = 0; (i < node->num_objs) && !found; i++) {
+ this = node->objs + i;
+
+ for (j = 0; j < test->num_objs; j++) {
+ that = test->objs + j;
+
+ if (this->obj != that->obj)
+ continue;
+
+ /* Only need to worry about writes */
+ if (this->read_only && that->read_only)
+ continue;
+
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ node->dep_list[node->num_deps] = test;
+ node->num_deps++;
+ }
+ }
+}
+
+static int i915_scheduler_queue_execbuffer_bypass(struct i915_scheduler_queue_entry *qe)
+{
+ struct drm_i915_private *dev_priv = to_i915(qe->params.dev);
+ struct i915_scheduler *scheduler = dev_priv->scheduler;
+ int ret;
+
+ scheduler->flags[qe->params.engine->id] |= I915_SF_SUBMITTING;
+ ret = dev_priv->gt.execbuf_final(&qe->params);
+ scheduler->flags[qe->params.engine->id] &= ~I915_SF_SUBMITTING;
+
+ /*
+ * Don't do any clean up on failure because the caller will
+ * do it all anyway.
+ */
+ if (ret)
+ return ret;
+
+ /* Need to release any resources held by the node: */
+ qe->status = I915_SQS_COMPLETE;
+ i915_scheduler_clean_node(qe);
+
+ return 0;
+}
+
+static inline uint32_t i915_scheduler_count_incomplete(struct i915_scheduler *scheduler)
+{
+ int e, incomplete = 0;
+
+ for (e = 0; e < I915_NUM_ENGINES; e++)
+ incomplete += scheduler->counts[e].queued + scheduler->counts[e].flying;
+
+ return incomplete;
+}
+
+/**
+ * i915_scheduler_queue_execbuffer - Submit a batch buffer request to the
+ * scheduler.
+ * @qe: The batch buffer request to be queued.
+ * The expectation is the qe passed in is a local stack variable. This
+ * function will copy its contents into a freshly allocated list node. The
+ * new node takes ownership of said contents so the original qe should simply
+ * be discarded and not cleaned up (i.e. don't free memory it points to or
+ * dereference objects it holds). The node is added to the scheduler's queue
+ * and the batch buffer will be submitted to the hardware at some future
+ * point in time (which may be immediately, before returning or may be quite
+ * a lot later).
+ */
+int i915_scheduler_queue_execbuffer(struct i915_scheduler_queue_entry *qe)
+{
+ struct drm_i915_private *dev_priv = to_i915(qe->params.dev);
+ struct i915_scheduler *scheduler = dev_priv->scheduler;
+ struct intel_engine_cs *engine = qe->params.engine;
+ struct i915_scheduler_queue_entry *node;
+ bool not_flying;
+ int i, e;
+ int incomplete;
+
+ /* Bypass the scheduler and send the buffer immediately? */
+ if (1/*!i915.enable_scheduler*/)
+ return i915_scheduler_queue_execbuffer_bypass(qe);
+
+ node = kmalloc(sizeof(*node), GFP_KERNEL);
+ if (!node)
+ return -ENOMEM;
+
+ *node = *qe;
+ INIT_LIST_HEAD(&node->link);
+ node->status = I915_SQS_QUEUED;
+ node->stamp = jiffies;
+ i915_gem_request_reference(node->params.request);
+
+ WARN_ON(node->params.request->scheduler_qe);
+ node->params.request->scheduler_qe = node;
+
+ /*
+ * Need to determine the number of incomplete entries in the list as
+ * that will be the maximum size of the dependency list.
+ *
+ * Note that the allocation must not be made with the spinlock acquired
+ * as kmalloc can sleep. However, the unlock/relock is safe because no
+ * new entries can be queued up during the unlock as the i915 driver
+ * mutex is still held. Entries could be removed from the list but that
+ * just means the dep_list will be over-allocated which is fine.
+ */
+ spin_lock_irq(&scheduler->lock);
+ incomplete = i915_scheduler_count_incomplete(scheduler);
+
+ /* Temporarily unlock to allocate memory: */
+ spin_unlock_irq(&scheduler->lock);
+ if (incomplete) {
+ node->dep_list = kmalloc_array(incomplete,
+ sizeof(*node->dep_list),
+ GFP_KERNEL);
+ if (!node->dep_list) {
+ kfree(node);
+ return -ENOMEM;
+ }
+ } else
+ node->dep_list = NULL;
+
+ spin_lock_irq(&scheduler->lock);
+ node->num_deps = 0;
+
+ if (node->dep_list) {
+ for (e = 0; e < I915_NUM_ENGINES; e++)
+ i915_generate_dependencies(scheduler, node, e);
+
+ WARN_ON(node->num_deps > incomplete);
+ }
+
+ node->priority = clamp(node->priority,
+ scheduler->priority_level_min,
+ scheduler->priority_level_max);
+
+ if ((node->priority > 0) && node->num_deps) {
+ i915_scheduler_priority_bump_clear(scheduler);
+
+ for (i = 0; i < node->num_deps; i++)
+ i915_scheduler_priority_bump(scheduler,
+ node->dep_list[i], node->priority);
+ }
+
+ list_add_tail(&node->link, &scheduler->node_queue[engine->id]);
+
+ not_flying = i915_scheduler_count_flying(scheduler, engine) <
+ scheduler->min_flying;
+
+ scheduler->counts[engine->id].queued++;
+
+ spin_unlock_irq(&scheduler->lock);
+
+ if (not_flying)
+ i915_scheduler_submit(engine);
+
+ return 0;
+}
+
+/**
+ * i915_scheduler_notify_request - Notify the scheduler that the given
+ * request has completed on the hardware.
+ * @req: Request structure which has completed
+ * @preempt: Did it complete pre-emptively?
+ * A sequence number has popped out of the hardware and the request handling
+ * code has mapped it back to a request and will mark that request complete.
+ * It also calls this function to notify the scheduler about the completion
+ * so the scheduler's node can be updated appropriately.
+ * Returns true if the request is scheduler managed, false if not. The return
+ * value is combined for all freshly completed requests and if any were true
+ * then i915_scheduler_wakeup() is called so the scheduler can do further
+ * processing (submit more work) at the end.
+ */
+bool i915_scheduler_notify_request(struct drm_i915_gem_request *req)
+{
+ struct drm_i915_private *dev_priv = to_i915(req->engine->dev);
+ struct i915_scheduler *scheduler = dev_priv->scheduler;
+ struct i915_scheduler_queue_entry *node = req->scheduler_qe;
+ unsigned long flags;
+
+ if (!node)
+ return false;
+
+ spin_lock_irqsave(&scheduler->lock, flags);
+
+ WARN_ON(!I915_SQS_IS_FLYING(node));
+
+ /* Node was in flight so mark it as complete. */
+ if (req->cancelled)
+ node->status = I915_SQS_DEAD;
+ else
+ node->status = I915_SQS_COMPLETE;
+
+ scheduler->counts[req->engine->id].flying--;
+
+ spin_unlock_irqrestore(&scheduler->lock, flags);
+
+ return true;
+}
+
+static int i915_scheduler_remove_dependent(struct i915_scheduler *scheduler,
+ struct i915_scheduler_queue_entry *remove)
+{
+ struct i915_scheduler_queue_entry *node;
+ int i, r;
+ int count = 0;
+
+ /*
+ * Ensure that a node is not being removed which is still dependent
+ * upon other (not completed) work. If that happens, it implies
+ * something has gone very wrong with the dependency tracking! Note
+ * that there is no need to worry if this node has been explicitly
+ * killed for some reason - it might be being killed before it got
+ * sent to the hardware.
+ */
+ if (remove->status != I915_SQS_DEAD) {
+ for (i = 0; i < remove->num_deps; i++)
+ if ((remove->dep_list[i]) &&
+ (!I915_SQS_IS_COMPLETE(remove->dep_list[i])))
+ count++;
+ WARN_ON(count);
+ }
+
+ /*
+ * Remove this node from the dependency lists of any other node which
+ * might be waiting on it.
+ */
+ for (r = 0; r < I915_NUM_ENGINES; r++) {
+ for_each_scheduler_node(node, r) {
+ for (i = 0; i < node->num_deps; i++) {
+ if (node->dep_list[i] != remove)
+ continue;
+
+ node->dep_list[i] = NULL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * i915_scheduler_wakeup - wake the scheduler's worker thread
+ * @dev: DRM device
+ * Called at the end of seqno interrupt processing if any request has
+ * completed that corresponds to a scheduler node.
+ */
+void i915_scheduler_wakeup(struct drm_device *dev)
+{
+ /* XXX: Need to call i915_scheduler_remove() via work handler. */
+}
+
+/**
+ * i915_scheduler_clean_node - free up any allocations/references
+ * associated with the given scheduler queue entry.
+ * @node: Queue entry structure which is complete
+ * After a give batch buffer completes on the hardware, all the information
+ * required to resubmit it is no longer required. However, the node entry
+ * itself might still be required for tracking purposes for a while longer.
+ * This function should be called as soon as the node is known to be complete
+ * so that these resources may be freed even though the node itself might
+ * hang around.
+ */
+void i915_scheduler_clean_node(struct i915_scheduler_queue_entry *node)
+{
+ if (!I915_SQS_IS_COMPLETE(node)) {
+ WARN(!node->params.request->cancelled,
+ "Cleaning active node: %d!\n", node->status);
+ return;
+ }
+
+ if (node->params.batch_obj) {
+ /*
+ * The batch buffer must be unpinned before it is unreferenced
+ * otherwise the unpin fails with a missing vma!?
+ */
+ if (node->params.dispatch_flags & I915_DISPATCH_SECURE)
+ i915_gem_execbuf_release_batch_obj(node->params.batch_obj);
+
+ node->params.batch_obj = NULL;
+ }
+
+ /* And anything else owned by the node: */
+ if (node->params.cliprects) {
+ kfree(node->params.cliprects);
+ node->params.cliprects = NULL;
+ }
+}
+
+static bool i915_scheduler_remove(struct i915_scheduler *scheduler,
+ struct intel_engine_cs *engine,
+ struct list_head *remove)
+{
+ struct i915_scheduler_queue_entry *node, *node_next;
+ bool do_submit;
+
+ spin_lock_irq(&scheduler->lock);
+
+ INIT_LIST_HEAD(remove);
+ list_for_each_entry_safe(node, node_next, &scheduler->node_queue[engine->id], link) {
+ if (!I915_SQS_IS_COMPLETE(node))
+ break;
+
+ list_del(&node->link);
+ list_add(&node->link, remove);
+
+ /* Strip the dependency info while the mutex is still locked */
+ i915_scheduler_remove_dependent(scheduler, node);
+
+ continue;
+ }
+
+ /*
+ * Release the interrupt reference count if there are no longer any
+ * nodes to worry about.
+ */
+ if (list_empty(&scheduler->node_queue[engine->id]) &&
+ (scheduler->flags[engine->id] & I915_SF_INTERRUPTS_ENABLED)) {
+ engine->irq_put(engine);
+ scheduler->flags[engine->id] &= ~I915_SF_INTERRUPTS_ENABLED;
+ }
+
+ /* Launch more packets now? */
+ do_submit = (scheduler->counts[engine->id].queued > 0) &&
+ (scheduler->counts[engine->id].flying < scheduler->min_flying);
+
+ spin_unlock_irq(&scheduler->lock);
+
+ return do_submit;
+}
+
+void i915_scheduler_process_work(struct intel_engine_cs *engine)
+{
+ struct drm_i915_private *dev_priv = to_i915(engine->dev);
+ struct i915_scheduler *scheduler = dev_priv->scheduler;
+ struct i915_scheduler_queue_entry *node;
+ bool do_submit;
+ struct list_head remove;
+
+ if (list_empty(&scheduler->node_queue[engine->id]))
+ return;
+
+ /* Remove completed nodes. */
+ do_submit = i915_scheduler_remove(scheduler, engine, &remove);
+
+ if (!do_submit && list_empty(&remove))
+ return;
+
+ /* Need to grab the pm lock outside of the mutex lock */
+ if (do_submit)
+ intel_runtime_pm_get(dev_priv);
+
+ mutex_lock(&engine->dev->struct_mutex);
+
+ if (do_submit)
+ i915_scheduler_submit(engine);
+
+ while (!list_empty(&remove)) {
+ node = list_first_entry(&remove, typeof(*node), link);
+ list_del(&node->link);
+
+ /* Free up all the DRM references */
+ i915_scheduler_clean_node(node);
+
+ /* And anything else owned by the node: */
+ node->params.request->scheduler_qe = NULL;
+ i915_gem_request_unreference(node->params.request);
+ kfree(node->dep_list);
+ kfree(node);
+ }
+
+ mutex_unlock(&engine->dev->struct_mutex);
+
+ if (do_submit)
+ intel_runtime_pm_put(dev_priv);
+}
diff --git a/drivers/gpu/drm/i915/i915_scheduler.h b/drivers/gpu/drm/i915/i915_scheduler.h
new file mode 100644
index 000000000000..c895c4cbc68e
--- /dev/null
+++ b/drivers/gpu/drm/i915/i915_scheduler.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#ifndef _I915_SCHEDULER_H_
+#define _I915_SCHEDULER_H_
+
+enum i915_scheduler_queue_status {
+ /* Limbo: */
+ I915_SQS_NONE = 0,
+ /* Not yet submitted to hardware: */
+ I915_SQS_QUEUED,
+ /* Popped from queue, ready to fly: */
+ I915_SQS_POPPED,
+ /* Sent to hardware for processing: */
+ I915_SQS_FLYING,
+ /* Finished processing on the hardware: */
+ I915_SQS_COMPLETE,
+ /* Killed by watchdog or catastrophic submission failure: */
+ I915_SQS_DEAD,
+ /* Limit value for use with arrays/loops */
+ I915_SQS_MAX
+};
+
+#define I915_SQS_IS_QUEUED(node) (((node)->status == I915_SQS_QUEUED))
+#define I915_SQS_IS_FLYING(node) (((node)->status == I915_SQS_FLYING))
+#define I915_SQS_IS_COMPLETE(node) (((node)->status == I915_SQS_COMPLETE) || \
+ ((node)->status == I915_SQS_DEAD))
+
+struct i915_scheduler_obj_entry {
+ struct drm_i915_gem_object *obj;
+ bool read_only;
+};
+
+struct i915_scheduler_queue_entry {
+ /* Any information required to submit this batch buffer to the hardware */
+ struct i915_execbuffer_params params;
+
+ /* -1023 = lowest priority, 0 = default, 1023 = highest */
+ int32_t priority;
+ bool bumped;
+
+ /* Objects referenced by this batch buffer */
+ struct i915_scheduler_obj_entry *objs;
+ int num_objs;
+
+ /* Batch buffers this one is dependent upon */
+ struct i915_scheduler_queue_entry **dep_list;
+ int num_deps;
+
+ enum i915_scheduler_queue_status status;
+ unsigned long stamp;
+
+ /* List of all scheduler queue entry nodes */
+ struct list_head link;
+};
+
+struct i915_scheduler_node_states {
+ uint32_t flying;
+ uint32_t queued;
+};
+
+struct i915_scheduler {
+ struct list_head node_queue[I915_NUM_ENGINES];
+ uint32_t flags[I915_NUM_ENGINES];
+ spinlock_t lock;
+
+ /* Node counts: */
+ struct i915_scheduler_node_states counts[I915_NUM_ENGINES];
+
+ /* Tuning parameters: */
+ int32_t priority_level_min;
+ int32_t priority_level_max;
+ int32_t priority_level_bump;
+ int32_t priority_level_preempt;
+ uint32_t min_flying;
+};
+
+/* Flag bits for i915_scheduler::flags */
+enum {
+ I915_SF_INTERRUPTS_ENABLED = (1 << 0),
+ I915_SF_SUBMITTING = (1 << 1),
+};
+
+bool i915_scheduler_is_enabled(struct drm_device *dev);
+int i915_scheduler_init(struct drm_device *dev);
+void i915_scheduler_destroy(struct drm_i915_private *dev_priv);
+void i915_scheduler_clean_node(struct i915_scheduler_queue_entry *node);
+int i915_scheduler_queue_execbuffer(struct i915_scheduler_queue_entry *qe);
+bool i915_scheduler_notify_request(struct drm_i915_gem_request *req);
+void i915_scheduler_wakeup(struct drm_device *dev);
+
+#endif /* _I915_SCHEDULER_H_ */