drm/nouveau: add per-channel mutex, use to lock access to drm's channel

This fixes a race condition between fbcon acceleration and TTM buffer
moves.  To reproduce:

- start X
- switch to vt and "while (true); do dmesg; done"
- switch to another vt and "sleep 2 && cat /path/to/debugfs/dri/0/evict_vram"
- switch back to vt running dmesg

We don't make use of this on any other channel yet, they're currently
protected by drm_global_mutex.  This will change in the near future.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index c41e1c2..d8817b4 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -683,17 +683,24 @@
 	int ret;
 
 	chan = nvbo->channel;
-	if (!chan || nvbo->no_vm)
+	if (!chan || nvbo->no_vm) {
 		chan = dev_priv->channel;
+		mutex_lock(&chan->mutex);
+	}
 
 	if (dev_priv->card_type < NV_50)
 		ret = nv04_bo_move_m2mf(chan, bo, &bo->mem, new_mem);
 	else
 		ret = nv50_bo_move_m2mf(chan, bo, &bo->mem, new_mem);
-	if (ret)
-		return ret;
+	if (ret == 0) {
+		ret = nouveau_bo_move_accel_cleanup(chan, nvbo, evict,
+						    no_wait_reserve,
+						    no_wait_gpu, new_mem);
+	}
 
-	return nouveau_bo_move_accel_cleanup(chan, nvbo, evict, no_wait_reserve, no_wait_gpu, new_mem);
+	if (chan == dev_priv->channel)
+		mutex_unlock(&chan->mutex);
+	return ret;
 }
 
 static int
diff --git a/drivers/gpu/drm/nouveau/nouveau_channel.c b/drivers/gpu/drm/nouveau/nouveau_channel.c
index 373950e..8636478 100644
--- a/drivers/gpu/drm/nouveau/nouveau_channel.c
+++ b/drivers/gpu/drm/nouveau/nouveau_channel.c
@@ -145,6 +145,7 @@
 	chan->file_priv = file_priv;
 	chan->vram_handle = vram_handle;
 	chan->gart_handle = tt_handle;
+	mutex_init(&chan->mutex);
 
 	NV_INFO(dev, "Allocating FIFO number %d\n", channel);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index 1c7db64..04bc56c 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -166,6 +166,8 @@
 	struct drm_device *dev;
 	int id;
 
+	struct mutex mutex;
+
 	/* owner of this fifo */
 	struct drm_file *file_priv;
 	/* mapping of the fifo itself */
diff --git a/drivers/gpu/drm/nouveau/nouveau_fbcon.c b/drivers/gpu/drm/nouveau/nouveau_fbcon.c
index 22e83ad..bc30dbe 100644
--- a/drivers/gpu/drm/nouveau/nouveau_fbcon.c
+++ b/drivers/gpu/drm/nouveau/nouveau_fbcon.c
@@ -62,11 +62,13 @@
 
 	ret = -ENODEV;
 	if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED)) {
+		mutex_lock(&dev_priv->channel->mutex);
 		if (dev_priv->card_type < NV_50)
 			ret = nv04_fbcon_fillrect(info, rect);
 		else
 		if (dev_priv->card_type < NV_C0)
 			ret = nv50_fbcon_fillrect(info, rect);
+		mutex_unlock(&dev_priv->channel->mutex);
 	}
 
 	if (ret == 0)
@@ -90,11 +92,13 @@
 
 	ret = -ENODEV;
 	if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED)) {
+		mutex_lock(&dev_priv->channel->mutex);
 		if (dev_priv->card_type < NV_50)
 			ret = nv04_fbcon_copyarea(info, image);
 		else
 		if (dev_priv->card_type < NV_C0)
 			ret = nv50_fbcon_copyarea(info, image);
+		mutex_unlock(&dev_priv->channel->mutex);
 	}
 
 	if (ret == 0)
@@ -118,11 +122,13 @@
 
 	ret = -ENODEV;
 	if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED)) {
+		mutex_lock(&dev_priv->channel->mutex);
 		if (dev_priv->card_type < NV_50)
 			ret = nv04_fbcon_imageblit(info, image);
 		else
 		if (dev_priv->card_type < NV_C0)
 			ret = nv50_fbcon_imageblit(info, image);
+		mutex_unlock(&dev_priv->channel->mutex);
 	}
 
 	if (ret == 0)
@@ -142,12 +148,15 @@
 	struct nouveau_channel *chan = dev_priv->channel;
 	int ret, i;
 
-	if (!chan || !chan->accel_done ||
+	if (!chan || !chan->accel_done || in_interrupt() ||
 	    info->state != FBINFO_STATE_RUNNING ||
 	    info->flags & FBINFO_HWACCEL_DISABLED)
 		return 0;
 
-	if (RING_SPACE(chan, 4)) {
+	mutex_lock(&chan->mutex);
+	ret = RING_SPACE(chan, 4);
+	if (ret) {
+		mutex_unlock(&chan->mutex);
 		nouveau_fbcon_gpu_lockup(info);
 		return 0;
 	}
@@ -158,6 +167,7 @@
 	OUT_RING(chan, 0);
 	nouveau_bo_wr32(chan->notifier_bo, chan->m2mf_ntfy + 3, 0xffffffff);
 	FIRE_RING(chan);
+	mutex_unlock(&chan->mutex);
 
 	ret = -EBUSY;
 	for (i = 0; i < 100000; i++) {
@@ -353,6 +363,8 @@
 	info->pixmap.flags = FB_PIXMAP_SYSTEM;
 	info->pixmap.scan_align = 1;
 
+	mutex_unlock(&dev->struct_mutex);
+
 	if (dev_priv->channel && !nouveau_nofbaccel) {
 		ret = -ENODEV;
 		if (dev_priv->card_type < NV_50)
@@ -373,7 +385,6 @@
 						nouveau_fb->base.height,
 						nvbo->bo.offset, nvbo);
 
-	mutex_unlock(&dev->struct_mutex);
 	vga_switcheroo_client_fb_set(dev->pdev, info);
 	return 0;