summary refs log tree commit diff
diff options
context:
space:
mode:
authorBen Skeggs <bskeggs@redhat.com>2012-08-10 13:47:56 +1000
committerBen Skeggs <bskeggs@redhat.com>2012-10-03 13:13:02 +1000
commit3acec63aee5d082f86218973912e682e84ec2a05 (patch)
treefbd45dda08d5039be919d006d3801a42978e294b
parent6fa8e62937687cb2db10d0e7c7f91eb73a20365c (diff)
downloadlinux-3acec63aee5d082f86218973912e682e84ec2a05.tar.gz
drm/nouveau/core: protect engine context list with hardirq-safe spinlock
IRQ handlers will need access to engine contexts.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
-rw-r--r--drivers/gpu/drm/nouveau/core/core/engctx.c74
-rw-r--r--drivers/gpu/drm/nouveau/core/core/engine.c1
-rw-r--r--drivers/gpu/drm/nouveau/core/include/core/engctx.h2
-rw-r--r--drivers/gpu/drm/nouveau/core/include/core/engine.h3
4 files changed, 60 insertions, 20 deletions
diff --git a/drivers/gpu/drm/nouveau/core/core/engctx.c b/drivers/gpu/drm/nouveau/core/core/engctx.c
index 50dc16d29f20..38c0612a5122 100644
--- a/drivers/gpu/drm/nouveau/core/core/engctx.c
+++ b/drivers/gpu/drm/nouveau/core/core/engctx.c
@@ -30,6 +30,25 @@
 
 #include <subdev/vm.h>
 
+static inline int
+nouveau_engctx_exists(struct nouveau_object *parent,
+		      struct nouveau_engine *engine, void **pobject)
+{
+	struct nouveau_engctx *engctx;
+	struct nouveau_object *parctx;
+
+	list_for_each_entry(engctx, &engine->contexts, head) {
+		parctx = nv_pclass(nv_object(engctx), NV_PARENT_CLASS);
+		if (parctx == parent) {
+			atomic_inc(&nv_object(engctx)->refcount);
+			*pobject = engctx;
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
 int
 nouveau_engctx_create_(struct nouveau_object *parent,
 		       struct nouveau_object *engobj,
@@ -41,23 +60,22 @@ nouveau_engctx_create_(struct nouveau_object *parent,
 	struct nouveau_client *client = nouveau_client(parent);
 	struct nouveau_engine *engine = nv_engine(engobj);
 	struct nouveau_subdev *subdev = nv_subdev(engine);
-	struct nouveau_engctx *engctx;
-	struct nouveau_object *ctxpar;
+	struct nouveau_object *engctx;
+	unsigned long save;
 	int ret;
 
-	/* use existing context for the engine if one is available */
-	mutex_lock(&subdev->mutex);
-	list_for_each_entry(engctx, &engine->contexts, head) {
-		ctxpar = nv_pclass(nv_object(engctx), NV_PARENT_CLASS);
-		if (ctxpar == parent) {
-			atomic_inc(&nv_object(engctx)->refcount);
-			*pobject = engctx;
-			mutex_unlock(&subdev->mutex);
-			return 1;
-		}
-	}
-	mutex_unlock(&subdev->mutex);
+	/* check if this engine already has a context for the parent object,
+	 * and reference it instead of creating a new one
+	 */
+	spin_lock_irqsave(&engine->lock, save);
+	ret = nouveau_engctx_exists(parent, engine, pobject);
+	spin_unlock_irqrestore(&engine->lock, save);
+	if (ret)
+		return ret;
 
+	/* create the new context, supports creating both raw objects and
+	 * objects backed by instance memory
+	 */
 	if (size) {
 		ret = nouveau_gpuobj_create_(parent, engobj, oclass,
 					     NV_ENGCTX_CLASS,
@@ -69,25 +87,43 @@ nouveau_engctx_create_(struct nouveau_object *parent,
 	}
 
 	engctx = *pobject;
-	if (engctx && client->vm)
-		atomic_inc(&client->vm->engref[nv_engidx(engobj)]);
 	if (ret)
 		return ret;
 
-	list_add(&engctx->head, &engine->contexts);
+	/* must take the lock again and re-check a context doesn't already
+	 * exist (in case of a race) - the lock had to be dropped before as
+	 * it's not possible to allocate the object with it held.
+	 */
+	spin_lock_irqsave(&engine->lock, save);
+	ret = nouveau_engctx_exists(parent, engine, pobject);
+	if (ret) {
+		spin_unlock_irqrestore(&engine->lock, save);
+		nouveau_object_ref(NULL, &engctx);
+		return ret;
+	}
+
+	if (client->vm)
+		atomic_inc(&client->vm->engref[nv_engidx(engobj)]);
+	list_add(&nv_engctx(engctx)->head, &engine->contexts);
+	spin_unlock_irqrestore(&engine->lock, save);
 	return 0;
 }
 
 void
 nouveau_engctx_destroy(struct nouveau_engctx *engctx)
 {
-	struct nouveau_object *engine = nv_object(engctx)->engine;
+	struct nouveau_object *engobj = nv_object(engctx)->engine;
+	struct nouveau_engine *engine = nv_engine(engobj);
 	struct nouveau_client *client = nouveau_client(engctx);
+	unsigned long save;
 
 	nouveau_gpuobj_unmap(&engctx->vma);
+	spin_lock_irqsave(&engine->lock, save);
 	list_del(&engctx->head);
+	spin_unlock_irqrestore(&engine->lock, save);
+
 	if (client->vm)
-		atomic_dec(&client->vm->engref[nv_engidx(engine)]);
+		atomic_dec(&client->vm->engref[nv_engidx(engobj)]);
 
 	if (engctx->base.size)
 		nouveau_gpuobj_destroy(&engctx->base);
diff --git a/drivers/gpu/drm/nouveau/core/core/engine.c b/drivers/gpu/drm/nouveau/core/core/engine.c
index ee2905b806c6..09b3bd502fd0 100644
--- a/drivers/gpu/drm/nouveau/core/core/engine.c
+++ b/drivers/gpu/drm/nouveau/core/core/engine.c
@@ -50,5 +50,6 @@ nouveau_engine_create_(struct nouveau_object *parent,
 	}
 
 	INIT_LIST_HEAD(&engine->contexts);
+	spin_lock_init(&engine->lock);
 	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/core/include/core/engctx.h b/drivers/gpu/drm/nouveau/core/include/core/engctx.h
index cbc9eb3a0d1a..3bc6ccd6cbd8 100644
--- a/drivers/gpu/drm/nouveau/core/include/core/engctx.h
+++ b/drivers/gpu/drm/nouveau/core/include/core/engctx.h
@@ -15,7 +15,7 @@ struct nouveau_engctx {
 	struct list_head head;
 };
 
-static inline void *
+static inline struct nouveau_engctx *
 nv_engctx(void *obj)
 {
 #if CONFIG_NOUVEAU_DEBUG >= NV_DBG_PARANOIA
diff --git a/drivers/gpu/drm/nouveau/core/include/core/engine.h b/drivers/gpu/drm/nouveau/core/include/core/engine.h
index 1a7b0a7455ca..666d06de77ec 100644
--- a/drivers/gpu/drm/nouveau/core/include/core/engine.h
+++ b/drivers/gpu/drm/nouveau/core/include/core/engine.h
@@ -11,7 +11,10 @@ struct nouveau_engine {
 	struct nouveau_subdev base;
 	struct nouveau_oclass *cclass;
 	struct nouveau_oclass *sclass;
+
 	struct list_head contexts;
+	spinlock_t lock;
+
 	void (*tile_prog)(struct nouveau_engine *, int region);
 	int  (*tlb_flush)(struct nouveau_engine *);
 };