drm/i915: implement interruptible sleeps in the overlay code

At least for the common case of userspace ioctls. When doing a
modeset operation, the wait is still uninterruptible. But considering
that failing to turn off the overlay when switching off the crtc it's
running on hangs the chip, it doesn't complicate matters _very_
much. There's just an unkillable X in addition to a black screen.
BUG() about it and explain in the code.

Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Eric Anholt <eric@anholt.net>
diff --git a/drivers/gpu/drm/i915/intel_overlay.c b/drivers/gpu/drm/i915/intel_overlay.c
index 85e07e4..972d715 100644
--- a/drivers/gpu/drm/i915/intel_overlay.c
+++ b/drivers/gpu/drm/i915/intel_overlay.c
@@ -222,6 +222,9 @@
 
 	BUG_ON(overlay->active);
 
+	overlay->active = 1;
+	overlay->hw_wedged = NEEDS_WAIT_FOR_FLIP;
+
 	BEGIN_LP_RING(6);
 	OUT_RING(MI_FLUSH);
 	OUT_RING(MI_NOOP);
@@ -231,15 +234,16 @@
 	OUT_RING(MI_NOOP);
 	ADVANCE_LP_RING();
 
-	ret = i915_lp_ring_sync(dev);
-	if (ret != 0) {
-		DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n");
-		overlay->hw_wedged = 1;
-		return 0;
-	}
+	overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+	if (overlay->last_flip_req == 0)
+		return -ENOMEM;
 
-	overlay->active = 1;
+	ret = i915_do_wait_request(dev, overlay->last_flip_req, 1);
+	if (ret != 0)
+		return ret;
 
+	overlay->hw_wedged = 0;
+	overlay->last_flip_req = 0;
 	return 0;
 }
 
@@ -283,7 +287,6 @@
 
 	if (overlay->last_flip_req != 0) {
 		ret = i915_do_wait_request(dev, overlay->last_flip_req, 0);
-
 		if (ret != 0)
 			return ret;
 
@@ -296,19 +299,24 @@
 	}
 
 	/* synchronous slowpath */
+	overlay->hw_wedged = RELEASE_OLD_VID;
+
 	BEGIN_LP_RING(2);
         OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP);
         OUT_RING(MI_NOOP);
         ADVANCE_LP_RING();
 
-	/* run in lockstep with the hw for easier testing */
-	ret = i915_lp_ring_sync(dev);
-	if (ret != 0) {
-		DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n");
-		overlay->hw_wedged = 1;
-	}
+	overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+	if (overlay->last_flip_req == 0)
+		return -ENOMEM;
 
-	return ret;
+	ret = i915_do_wait_request(dev, overlay->last_flip_req, 1);
+	if (ret != 0)
+		return ret;
+
+	overlay->hw_wedged = 0;
+	overlay->last_flip_req = 0;
+	return 0;
 }
 
 /* overlay needs to be disabled in OCMD reg */
@@ -329,6 +337,8 @@
 	flip_addr |= OFC_UPDATE;
 
 	/* wait for overlay to go idle */
+	overlay->hw_wedged = SWITCH_OFF_STAGE_1;
+
 	BEGIN_LP_RING(6);
 	OUT_RING(MI_FLUSH);
 	OUT_RING(MI_NOOP);
@@ -338,14 +348,17 @@
         OUT_RING(MI_NOOP);
         ADVANCE_LP_RING();
 
-	ret = i915_lp_ring_sync(dev);
-	if (ret != 0) {
-		DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n");
-		overlay->hw_wedged = 1;
+	overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+	if (overlay->last_flip_req == 0)
+		return -ENOMEM;
+
+	ret = i915_do_wait_request(dev, overlay->last_flip_req, 1);
+	if (ret != 0)
 		return ret;
-	}
 
 	/* turn overlay off */
+	overlay->hw_wedged = SWITCH_OFF_STAGE_2;
+
 	BEGIN_LP_RING(6);
         OUT_RING(MI_FLUSH);
         OUT_RING(MI_NOOP);
@@ -355,18 +368,100 @@
         OUT_RING(MI_NOOP);
 	ADVANCE_LP_RING();
 
-	ret = i915_lp_ring_sync(dev);
-	if (ret != 0) {
-		DRM_ERROR("intel overlay: ring sync failed, hw likely wedged\n");
-		overlay->hw_wedged = 1;
+	overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+	if (overlay->last_flip_req == 0)
+		return -ENOMEM;
+
+	ret = i915_do_wait_request(dev, overlay->last_flip_req, 1);
+	if (ret != 0)
 		return ret;
-	}
 
 	overlay->active = 0;
-
+	overlay->hw_wedged = 0;
+	overlay->last_flip_req = 0;
 	return ret;
 }
 
+/* recover from an interruption due to a signal
+ * We have to be careful not to repeat work forever an make forward progess. */
+int intel_overlay_recover_from_interrupt(struct intel_overlay *overlay,
+					 int interruptible)
+{
+	struct drm_device *dev = overlay->dev;
+        drm_i915_private_t *dev_priv = dev->dev_private;
+	struct drm_gem_object *obj;
+	u32 flip_addr;
+	int ret;
+	RING_LOCALS;
+
+	if (overlay->hw_wedged == HW_WEDGED)
+		return -EIO;
+
+	if (overlay->last_flip_req == 0) {
+		overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+		if (overlay->last_flip_req == 0)
+			return -ENOMEM;
+	}
+
+	ret = i915_do_wait_request(dev, overlay->last_flip_req, interruptible);
+	if (ret != 0)
+		return ret;
+
+	switch (overlay->hw_wedged) {
+		case RELEASE_OLD_VID:
+			obj = overlay->old_vid_bo->obj;
+			i915_gem_object_unpin(obj);
+			drm_gem_object_unreference(obj);
+			overlay->old_vid_bo = NULL;
+			break;
+		case SWITCH_OFF_STAGE_1:
+			flip_addr = overlay->flip_addr;
+			flip_addr |= OFC_UPDATE;
+
+			overlay->hw_wedged = SWITCH_OFF_STAGE_2;
+
+			BEGIN_LP_RING(6);
+			OUT_RING(MI_FLUSH);
+			OUT_RING(MI_NOOP);
+			OUT_RING(MI_OVERLAY_FLIP | MI_OVERLAY_OFF);
+			OUT_RING(flip_addr);
+			OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_OVERLAY_FLIP);
+			OUT_RING(MI_NOOP);
+			ADVANCE_LP_RING();
+
+			overlay->last_flip_req = i915_add_request(dev, NULL, 0);
+			if (overlay->last_flip_req == 0)
+				return -ENOMEM;
+
+			ret = i915_do_wait_request(dev, overlay->last_flip_req,
+					interruptible);
+			if (ret != 0)
+				return ret;
+
+		case SWITCH_OFF_STAGE_2:
+			printk("switch off 2\n");
+
+			BUG_ON(!overlay->vid_bo);
+			obj = overlay->vid_bo->obj;
+
+			i915_gem_object_unpin(obj);
+			drm_gem_object_unreference(obj);
+			overlay->vid_bo = NULL;
+
+			overlay->crtc->overlay = NULL;
+			overlay->crtc = NULL;
+
+			overlay->active = 0;
+			break;
+		default:
+			BUG_ON(overlay->hw_wedged != NEEDS_WAIT_FOR_FLIP);
+	}
+
+	overlay->hw_wedged = 0;
+	overlay->last_flip_req = 0;
+	return 0;
+}
+
 /* Wait for pending overlay flip and release old frame.
  * Needs to be called before the overlay register are changed
  * via intel_overlay_(un)map_regs_atomic */
@@ -375,13 +470,15 @@
 	int ret;
 	struct drm_gem_object *obj;
 
+	/* only wait if there is actually an old frame to release to
+	 * guarantee forward progress */
+	if (!overlay->old_vid_bo)
+		return 0;
+
 	ret = intel_overlay_wait_flip(overlay);
 	if (ret != 0)
 		return ret;
 
-	if (!overlay->old_vid_bo)
-		return 0;
-
 	obj = overlay->old_vid_bo->obj;
 	i915_gem_object_unpin(obj);
 	drm_gem_object_unreference(obj);
@@ -646,9 +743,6 @@
 	BUG_ON(!mutex_is_locked(&dev->mode_config.mutex));
 	BUG_ON(!overlay);
 
-	if (overlay->hw_wedged)
-		return -EBUSY;
-
 	ret = intel_overlay_release_old_vid(overlay);
 	if (ret != 0)
 		return ret;
@@ -761,6 +855,9 @@
 	intel_overlay_unmap_regs_atomic(overlay);
 
 	ret = intel_overlay_off(overlay);
+	if (ret != 0)
+		return ret;
+
 	/* never have the overlay hw on without showing a frame */
 	BUG_ON(!overlay->vid_bo);
 	obj = overlay->vid_bo->obj;
@@ -1002,6 +1099,12 @@
 	mutex_lock(&dev->mode_config.mutex);
 	mutex_lock(&dev->struct_mutex);
 
+	if (overlay->hw_wedged) {
+		ret = intel_overlay_recover_from_interrupt(overlay, 1);
+		if (ret != 0)
+			goto out_unlock;
+	}
+
 	if (overlay->crtc != crtc) {
 		struct drm_display_mode *mode = &crtc->base.mode;
 		ret = intel_overlay_switch_off(overlay);