[PATCH v3] drm/atomic-helper: tear down HPD/polling in drm_atomic_helper_shutdown()
From: Mahadevan P
Date: Mon Sep 21 2026 - 07:34:20 EST
drm_atomic_helper_shutdown() disables all CRTCs but leaves output
polling and IRQ-driven hot-plug detection running. On reboot, a late
DP hot-plug-detect (HPD) IRQ can fire after apps_smmu has already
disabled translation for the display subsystem, causing the HPD
thread to kick off a new modeset that drives DPU/DP hardware and DMA
through a stale IOMMU mapping.
drm_atomic_helper_shutdown() disables all CRTCs first, but a pending
HPD IRQ thread wakes up afterwards, reads the DPCD, and fires an
unsolicited hotplug event that triggers a second atomic commit
turning the display back on -- right as the IOMMU is disabling
translation:
systemd-shutdown[1]: Rebooting.
msm_dpu: drm_atomic_commit: committing (shutdown disabling CRTCs)
arm-smmu 3da0000.iommu: disabling translation
msm_dpu: drm_dp_read_dpcd_caps (late HPD IRQ thread wakes up)
msm_dpu: drm_sysfs_connector_hotplug_event: DP-1 hotplug event
msm_dpu: drm_client_modeset_probe: DP-1 found preferred mode
msm_dpu: drm_atomic_commit: committing (unsolicited, re-enables display)
dpu_crtc_commit_kickoff: crtc94 first commit
arm-smmu 15200000.iommu: disabling translation
Call drm_kms_helper_poll_disable() to tear this down: it stops the
output poll worker and calls each connector's
&drm_connector_helper_funcs.disable_hpd, which for HPD-capable bridges
masks the interrupt in hardware and then waits for an in-flight HPD
handler under bridge->hpd_mutex. Suspend the in-kernel clients as
well, so that a hotplug event which still gets through is recorded in
client->hotplug_pending instead of being probed and committed.
Reported on Qualcomm platforms such as lemans-evk and monaco-evk
during reboot stress testing.
Assisted-by: LLM
Signed-off-by: Mahadevan P <mahadevan.p@xxxxxxxxxxxxxxxx>
---
Changes in v3:
- Use drm_kms_helper_poll_disable() instead of drm_kms_helper_poll_fini(),
guarded on mode_config.poll_enabled. (Sashiko AI review)
- Also suspend the in-kernel clients. Masking HPD is not sufficient on its
own: it only covers connectors notifying through drm_bridge_hpd_notify(),
and a forced GETCONNECTOR re-arms HPD via
drm_helper_probe_single_connector_modes() while poll_enabled is still set.
drm_client_dev_suspend() keeps the resulting event from reaching a commit,
which is the drm_client_modeset_probe() path. (Sashiko AI review)
- Reworded the kerneldoc; v2 claimed only that polling and HPD were torn down
via drm_kms_helper_poll_fini().
- Link to v2: https://lore.kernel.org/r/20260730-dpshutdown-v2-1-441fc5543bed@xxxxxxxxxxxxxxxx
Changes in v2:
- Dropped the drm/msm/dp local .shutdown patch (disable_irq()/
synchronize_irq() in dp_display.c)., fixed this at the DRM core
level instead so every driver benefits, not just msm_dp.(Dmitry).
- Link to v1: https://lore.kernel.org/r/20260717-dpshutdown-v1-1-b062c2f7dfb1@xxxxxxxxxxxxxxxx
---
drivers/gpu/drm/drm_atomic_helper.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 9d006f98413a..3a23777aa00f 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -35,6 +35,7 @@
#include <drm/drm_atomic_uapi.h>
#include <drm/drm_blend.h>
#include <drm/drm_bridge.h>
+#include <drm/drm_client_event.h>
#include <drm/drm_colorop.h>
#include <drm/drm_damage_helper.h>
#include <drm/drm_device.h>
@@ -42,6 +43,7 @@
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
#include <drm/drm_self_refresh_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_writeback.h>
@@ -3676,6 +3678,11 @@ EXPORT_SYMBOL(drm_atomic_helper_reset_crtc);
*
* This is just a convenience wrapper around drm_atomic_helper_disable_all(),
* and it is the atomic version of drm_helper_force_disable_all().
+ *
+ * Before disabling the CRTCs this stops output polling, masks hot-plug
+ * detection and suspends the in-kernel clients, so that a hotplug event cannot
+ * probe and commit a new mode once the CRTCs are down. Polling is disabled but
+ * not finalized; drivers must still call drm_kms_helper_poll_fini().
*/
void drm_atomic_helper_shutdown(struct drm_device *dev)
{
@@ -3685,6 +3692,14 @@ void drm_atomic_helper_shutdown(struct drm_device *dev)
if (dev == NULL)
return;
+ /*
+ * Don't disable polling if it was never initialized
+ */
+ if (dev->mode_config.poll_enabled)
+ drm_kms_helper_poll_disable(dev);
+
+ drm_client_dev_suspend(dev);
+
DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret);
ret = drm_atomic_helper_disable_all(dev, &ctx);
---
base-commit: 3f2425f5b5bbbdd991ca9cdfd5502e68d8895998
change-id: 20260717-dpshutdown-047912fcf60b
Best regards,
--
Mahadevan P <mahadevan.p@xxxxxxxxxxxxxxxx>