From: Takashi Iwai <tiwai@suse.de>
Date: Wed, 29 Mar 2017 14:49:25 +0200
Subject: [PATCH] drm/i915: Fix crash after S3 resume with DP MST mode change
References: bsc#1029634
Patch-mainline: Submitted, intel-gfx ML

We've got a bug report showing that Skylake Dell machines with a
docking station causes a kernel panic after S3 resume and modeset.
The details are found in the openSUSE bugzilla entry below.  The
typical test procedure is:

- Laptop is Dell Latitude with eDP (1366x768)
- Boot with docking station connected to a DP (1920x1080)
- Login, change the mode via
  xrandr --output eDP-1 --auto --output DP-1-1 --auto --left-of eDP-1
- Suspend, and close the lid after the suspend
  (or close the lid to trigger the suspend)
- Undock while keeping the lid closed.
- Open the lid, which triggers the resume;
  the machine wakes up well, and X shows up.  No problem, so far.
- Suspend again, close the lid.
- Dock again while keeping the lid closed.
- Open the lid, triggering the resume; this wakes up still fine.
- At this moment, run xrandr again to re-setup DP-1
  xrandr --output eDP-1 --auto --output DP-1-1 --auto --left-of eDP-1
  ==> This triggers a hard crash.

I could bisect it, and this leaded to the commit a16b7658f4e0
("drm/i915: Call intel_dp_mst_resume() before resuming displays").

Unfortunately, no crash dump / log could be taken because the machine
gets screwed up including the whole network stack when it hits this
problem, and neither kdump nor netconsole worked.  So, currently it's
still pretty unclear what really happened.

But, eventually I found a workaround: just skip
drm_dp_mst_topology_mgr_resume() calls in intel_dp_mst_resume().
They should be handled in hpd later in anyway, so we can skip them
gracefully.

This patch addresses the crash by the workaround above; for a
completeness, the failed resume port is still logged in the debug
level.

V1->v2: just ignore the drm_dp_mst_topology_mgr_resume() error codes
        instead of postponing.

Note for SLE12-SP2 port: the drm_dp_mst_topology_mgr_resume() has to be
  executed manually after resume for SP2 kernel unlike the 4.11 upstream,
  so an extra function is called for doing this.  It's the same as my v1
  patch in the end.

Fixes: a16b7658f4e0 ("drm/i915: Call intel_dp_mst_resume() before resuming displays")
Bugzilla: https://bugzilla.suse.com/show_bug.cgi?id=1029634
Reviewed-by: Lyude <lyude@redhat.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>

---
 drivers/gpu/drm/i915/i915_drv.c  |    5 ++++-
 drivers/gpu/drm/i915/intel_dp.c  |   27 +++++++++++++++++++++++++--
 drivers/gpu/drm/i915/intel_drv.h |    3 ++-
 3 files changed, 31 insertions(+), 4 deletions(-)

--- a/drivers/gpu/drm/i915/intel_dp.c
+++ b/drivers/gpu/drm/i915/intel_dp.c
@@ -6245,9 +6245,10 @@ void intel_dp_mst_suspend(struct drm_dev
 	}
 }
 
-void intel_dp_mst_resume(struct drm_device *dev)
+int intel_dp_mst_resume(struct drm_device *dev)
 {
 	struct drm_i915_private *dev_priv = dev->dev_private;
+	int pending = 0;
 	int i;
 
 	for (i = 0; i < I915_MAX_PORTS; i++) {
@@ -6262,8 +6263,30 @@ void intel_dp_mst_resume(struct drm_devi
 
 			ret = drm_dp_mst_topology_mgr_resume(&intel_dig_port->dp.mst_mgr);
 			if (ret != 0) {
-				intel_dp_check_mst_status(&intel_dig_port->dp);
+				pending |= 1 << i;
 			}
 		}
 	}
+
+	return pending;
+}
+
+void intel_dp_mst_resume_post(struct drm_device *dev, int pending)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	int i;
+
+	for (i = 0; i < I915_MAX_PORTS; i++) {
+		struct intel_digital_port *intel_dig_port = dev_priv->hotplug.irq_port[i];
+		if (!intel_dig_port)
+			continue;
+
+		if (intel_dig_port->base.type != INTEL_OUTPUT_DISPLAYPORT ||
+		    !intel_dig_port->dp.can_mst)
+			continue;
+
+		if (pending & (1 << i))
+			intel_dp_check_mst_status(&intel_dig_port->dp);
+	}
 }
+
--- a/drivers/gpu/drm/i915/i915_drv.c
+++ b/drivers/gpu/drm/i915/i915_drv.c
@@ -747,6 +747,7 @@ int i915_suspend_switcheroo(struct drm_d
 static int i915_drm_resume(struct drm_device *dev)
 {
 	struct drm_i915_private *dev_priv = dev->dev_private;
+	int mst_pending;
 
 	mutex_lock(&dev->struct_mutex);
 	i915_gem_restore_gtt_mappings(dev);
@@ -784,12 +785,14 @@ static int i915_drm_resume(struct drm_de
 		dev_priv->display.hpd_irq_setup(dev);
 	spin_unlock_irq(&dev_priv->irq_lock);
 
-	intel_dp_mst_resume(dev);
+	mst_pending = intel_dp_mst_resume(dev);
 
 	drm_modeset_lock_all(dev);
 	intel_display_resume(dev);
 	drm_modeset_unlock_all(dev);
 
+	intel_dp_mst_resume_post(dev, mst_pending);
+
 	/*
 	 * ... but also need to make sure that hotplug processing
 	 * doesn't cause havoc. Like in the driver load code we don't
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -1223,7 +1223,8 @@ void intel_edp_panel_on(struct intel_dp
 void intel_edp_panel_off(struct intel_dp *intel_dp);
 void intel_dp_add_properties(struct intel_dp *intel_dp, struct drm_connector *connector);
 void intel_dp_mst_suspend(struct drm_device *dev);
-void intel_dp_mst_resume(struct drm_device *dev);
+int intel_dp_mst_resume(struct drm_device *dev);
+void intel_dp_mst_resume_post(struct drm_device *dev, int pending);
 int intel_dp_max_link_rate(struct intel_dp *intel_dp);
 int intel_dp_rate_select(struct intel_dp *intel_dp, int rate);
 void intel_dp_hot_plug(struct intel_encoder *intel_encoder);
