diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index 19773c9..3112bf3 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -1703,11 +1703,26 @@ static void igdng_crtc_dpms(struct drm_crtc *crtc, int mode)
 static void intel_crtc_dpms_overlay(struct intel_crtc *intel_crtc, bool enable)
 {
 	struct intel_overlay *overlay;
+	int ret;
 
 	if (!enable && intel_crtc->overlay) {
 		overlay = intel_crtc->overlay;
 		mutex_lock(&overlay->dev->struct_mutex);
-		intel_overlay_switch_off(overlay);
+		for (;;) {
+			ret = intel_overlay_switch_off(overlay);
+			if (ret == 0)
+				break;
+
+			ret = intel_overlay_recover_from_interrupt(overlay, 0);
+			if (ret != 0) {
+				/* overlay doesn't react anymore. Usually
+				 * results in a black screen and an unkillable
+				 * X server. */
+				BUG();
+				overlay->hw_wedged = HW_WEDGED;
+				break;
+			}
+		}
 		mutex_unlock(&overlay->dev->struct_mutex);
 	}
 	/* Let userspace switch the overlay on again. In most cases userspace
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 9eea5c7..8a0fddf 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -126,8 +126,13 @@ struct intel_overlay {
 	struct drm_i915_gem_object *reg_bo;
 	void *virt_addr;
 	/* flip handling */
-	int hw_wedged;
 	uint32_t last_flip_req;
+	int hw_wedged;
+#define HW_WEDGED		1
+#define NEEDS_WAIT_FOR_FLIP	2
+#define RELEASE_OLD_VID		3
+#define SWITCH_OFF_STAGE_1	4
+#define SWITCH_OFF_STAGE_2	5
 };
 
 struct intel_crtc {
@@ -207,6 +212,8 @@ extern int intel_framebuffer_create(struct drm_device *dev,
 extern void intel_setup_overlay(struct drm_device *dev);
 extern void intel_cleanup_overlay(struct drm_device *dev);
 extern int intel_overlay_switch_off(struct intel_overlay *overlay);
+extern int intel_overlay_recover_from_interrupt(struct intel_overlay *overlay,
+						int interruptible);
 extern int intel_overlay_put_image(struct drm_device *dev, void *data,
 				   struct drm_file *file_priv);
 extern int intel_overlay_attrs(struct drm_device *dev, void *data,
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 @@ static int intel_overlay_on(struct intel_overlay *overlay)
 
 	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 @@ static int intel_overlay_on(struct intel_overlay *overlay)
 	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 @@ static int intel_overlay_wait_flip(struct intel_overlay *overlay)
 
 	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 @@ static int intel_overlay_wait_flip(struct intel_overlay *overlay)
 	}
 
 	/* 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 @@ static int intel_overlay_off(struct intel_overlay *overlay)
 	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 @@ static int intel_overlay_off(struct intel_overlay *overlay)
         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 @@ static int intel_overlay_off(struct intel_overlay *overlay)
         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 @@ static int intel_overlay_release_old_vid(struct intel_overlay *overlay)
 	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 @@ int intel_overlay_do_put_image(struct intel_overlay *overlay,
 	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 @@ int intel_overlay_switch_off(struct intel_overlay *overlay)
 	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 @@ int intel_overlay_put_image(struct drm_device *dev, void *data,
 	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);
