Skip to content

Commit e8a8fed

Browse files
Lyudejnikula
authored andcommitted
drm/i915: Block fbdev HPD processing during suspend
When resuming, we check whether or not any previously connected MST topologies are still present and if so, attempt to resume them. If this fails, we disable said MST topologies and fire off a hotplug event so that userspace knows to reprobe. However, sending a hotplug event involves calling drm_fb_helper_hotplug_event(), which in turn results in fbcon doing a connector reprobe in the caller's thread - something we can't do at the point in which i915 calls drm_dp_mst_topology_mgr_resume() since hotplugging hasn't been fully initialized yet. This currently causes some rather subtle but fatal issues. For example, on my T480s the laptop dock connected to it usually disappears during a suspend cycle, and comes back up a short while after the system has been resumed. This guarantees pretty much every suspend and resume cycle, drm_dp_mst_topology_mgr_set_mst(mgr, false); will be caused and in turn, a connector hotplug will occur. Now it's Rute Goldberg time: when the connector hotplug occurs, i915 reprobes /all/ of the connectors, including eDP. However, eDP probing requires that we power on the panel VDD which in turn, grabs a wakeref to the appropriate power domain on the GPU (on my T480s, this is the PORT_DDI_A_IO domain). This is where things start breaking, since this all happens before intel_power_domains_enable() is called we end up leaking the wakeref that was acquired and never releasing it later. Come next suspend/resume cycle, this causes us to fail to shut down the GPU properly, which causes it not to resume properly and die a horrible complicated death. (as a note: this only happens when there's both an eDP panel and MST topology connected which is removed mid-suspend. One or the other seems to always be OK). We could try to fix the VDD wakeref leak, but this doesn't seem like it's worth it at all since we aren't able to handle hotplug detection while resuming anyway. So, let's go with a more robust solution inspired by nouveau: block fbdev from handling hotplug events until we resume fbdev. This allows us to still send sysfs hotplug events to be handled later by user space while we're resuming, while also preventing us from actually processing any hotplug events we receive until it's safe. This fixes the wakeref leak observed on the T480s and as such, also fixes suspend/resume with MST topologies connected on this machine. Changes since v2: * Don't call drm_fb_helper_hotplug_event() under lock, do it after lock (Chris Wilson) * Don't call drm_fb_helper_hotplug_event() in intel_fbdev_output_poll_changed() under lock (Chris Wilson) * Always set ifbdev->hpd_waiting (Chris Wilson) Signed-off-by: Lyude Paul <lyude@redhat.com> Fixes: 0e32b39 ("drm/i915: add DP 1.2 MST support (v0.7)") Cc: Todd Previte <tprevite@gmail.com> Cc: Dave Airlie <airlied@redhat.com> Cc: Jani Nikula <jani.nikula@linux.intel.com> Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com> Cc: Rodrigo Vivi <rodrigo.vivi@intel.com> Cc: Imre Deak <imre.deak@intel.com> Cc: intel-gfx@lists.freedesktop.org Cc: <stable@vger.kernel.org> # v3.17+ Reviewed-by: Chris Wilson <chris@chris-wilson.co.uk> Link: https://patchwork.freedesktop.org/patch/msgid/20190129191001.442-2-lyude@redhat.com (cherry picked from commit fe5ec65) Signed-off-by: Jani Nikula <jani.nikula@intel.com>
1 parent d8b879b commit e8a8fed

File tree

2 files changed

+42
-1
lines changed

2 files changed

+42
-1
lines changed

drivers/gpu/drm/i915/intel_drv.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,16 @@ struct intel_fbdev {
209209
unsigned long vma_flags;
210210
async_cookie_t cookie;
211211
int preferred_bpp;
212+
213+
/* Whether or not fbdev hpd processing is temporarily suspended */
214+
bool hpd_suspended : 1;
215+
/* Set when a hotplug was received while HPD processing was
216+
* suspended
217+
*/
218+
bool hpd_waiting : 1;
219+
220+
/* Protects hpd_suspended */
221+
struct mutex hpd_lock;
212222
};
213223

214224
struct intel_encoder {

drivers/gpu/drm/i915/intel_fbdev.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ int intel_fbdev_init(struct drm_device *dev)
679679
if (ifbdev == NULL)
680680
return -ENOMEM;
681681

682+
mutex_init(&ifbdev->hpd_lock);
682683
drm_fb_helper_prepare(dev, &ifbdev->helper, &intel_fb_helper_funcs);
683684

684685
if (!intel_fbdev_init_bios(dev, ifbdev))
@@ -752,6 +753,26 @@ void intel_fbdev_fini(struct drm_i915_private *dev_priv)
752753
intel_fbdev_destroy(ifbdev);
753754
}
754755

756+
/* Suspends/resumes fbdev processing of incoming HPD events. When resuming HPD
757+
* processing, fbdev will perform a full connector reprobe if a hotplug event
758+
* was received while HPD was suspended.
759+
*/
760+
static void intel_fbdev_hpd_set_suspend(struct intel_fbdev *ifbdev, int state)
761+
{
762+
bool send_hpd = false;
763+
764+
mutex_lock(&ifbdev->hpd_lock);
765+
ifbdev->hpd_suspended = state == FBINFO_STATE_SUSPENDED;
766+
send_hpd = !ifbdev->hpd_suspended && ifbdev->hpd_waiting;
767+
ifbdev->hpd_waiting = false;
768+
mutex_unlock(&ifbdev->hpd_lock);
769+
770+
if (send_hpd) {
771+
DRM_DEBUG_KMS("Handling delayed fbcon HPD event\n");
772+
drm_fb_helper_hotplug_event(&ifbdev->helper);
773+
}
774+
}
775+
755776
void intel_fbdev_set_suspend(struct drm_device *dev, int state, bool synchronous)
756777
{
757778
struct drm_i915_private *dev_priv = to_i915(dev);
@@ -773,6 +794,7 @@ void intel_fbdev_set_suspend(struct drm_device *dev, int state, bool synchronous
773794
*/
774795
if (state != FBINFO_STATE_RUNNING)
775796
flush_work(&dev_priv->fbdev_suspend_work);
797+
776798
console_lock();
777799
} else {
778800
/*
@@ -800,17 +822,26 @@ void intel_fbdev_set_suspend(struct drm_device *dev, int state, bool synchronous
800822

801823
drm_fb_helper_set_suspend(&ifbdev->helper, state);
802824
console_unlock();
825+
826+
intel_fbdev_hpd_set_suspend(ifbdev, state);
803827
}
804828

805829
void intel_fbdev_output_poll_changed(struct drm_device *dev)
806830
{
807831
struct intel_fbdev *ifbdev = to_i915(dev)->fbdev;
832+
bool send_hpd;
808833

809834
if (!ifbdev)
810835
return;
811836

812837
intel_fbdev_sync(ifbdev);
813-
if (ifbdev->vma || ifbdev->helper.deferred_setup)
838+
839+
mutex_lock(&ifbdev->hpd_lock);
840+
send_hpd = !ifbdev->hpd_suspended;
841+
ifbdev->hpd_waiting = true;
842+
mutex_unlock(&ifbdev->hpd_lock);
843+
844+
if (send_hpd && (ifbdev->vma || ifbdev->helper.deferred_setup))
814845
drm_fb_helper_hotplug_event(&ifbdev->helper);
815846
}
816847

0 commit comments

Comments
 (0)