diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 0e27d45feba9..350348961ab3 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -1179,9 +1179,21 @@ static int drm_state_info(struct seq_file *m, void *data)
 	return 0;
 }
 
+static int drm_underrun_info(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct drm_device *dev = node->minor->dev;
+	struct drm_printer p = drm_seq_file_printer(m);
+
+	drm_printf(&p, "%c\n", atomic_read(&dev->underrun) ? 'Y' : 'N');
+
+	return 0;
+}
+
 /* any use in debugfs files to dump individual planes/crtc/etc? */
 static const struct drm_info_list drm_atomic_debugfs_list[] = {
 	{"state", drm_state_info, 0},
+	{"underrun", drm_underrun_info, 0 },
 };
 
 int drm_atomic_debugfs_init(struct drm_minor *minor)
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 6f66777dca4b..9956c2928c5b 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -1456,6 +1456,8 @@ void drm_atomic_helper_commit_tail(struct drm_atomic_state *old_state)
 {
 	struct drm_device *dev = old_state->dev;
 
+	drm_atomic_helper_underrun_stop(dev);
+
 	drm_atomic_helper_commit_modeset_disables(dev, old_state);
 
 	drm_atomic_helper_commit_planes(dev, old_state, 0);
@@ -1466,6 +1468,8 @@ void drm_atomic_helper_commit_tail(struct drm_atomic_state *old_state)
 
 	drm_atomic_helper_commit_hw_done(old_state);
 
+	drm_atomic_helper_underrun_start(dev);
+
 	drm_atomic_helper_wait_for_vblanks(dev, old_state);
 
 	drm_atomic_helper_cleanup_planes(dev, old_state);
diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h
index 25ca0097563e..f712144dd26d 100644
--- a/include/drm/drm_atomic_helper.h
+++ b/include/drm/drm_atomic_helper.h
@@ -221,4 +221,57 @@ drm_atomic_plane_disabling(struct drm_plane_state *old_plane_state,
 	return old_plane_state->crtc && !new_plane_state->crtc;
 }
 
+/**
+ * drm_atomic_helper_underrun_set() - Set the underrun bit
+ * @dev: device the underrun happened on
+ *
+ * This function should be called when an underrun happens after an update
+ * has been committed to the device.
+ * Underrun interrupts are masked to avoid flooding the CPU with useless
+ * interrupts.
+ */
+static inline void drm_atomic_helper_underrun_set(struct drm_device *dev)
+{
+	atomic_set(&dev->underrun, 1);
+
+	if (dev->mode_config.funcs->atomic_mask_underrun)
+		dev->mode_config.funcs->atomic_mask_underrun(dev);
+}
+
+/**
+ * drm_atomic_helper_underrun_stop() - Stop generating underrun events
+ * @dev: device we want to stop underrun on
+ *
+ * This function should be called in the atomic commit path, just before
+ * committing changes to the hardware.
+ * The underrun interrupt is simply masked prior to getting underrun events
+ * while we are applying the new config.
+ */
+static inline void drm_atomic_helper_underrun_stop(struct drm_device *dev)
+{
+	if (dev->mode_config.funcs->atomic_mask_underrun)
+		dev->mode_config.funcs->atomic_mask_underrun(dev);
+}
+
+/**
+ * drm_atomic_helper_underrun_start() - Start listening to underrun events
+ * @dev: device we want to start underrun on
+ *
+ * This function should be called in the atomic commit path, just after
+ * the changes have been committed to the hardware.
+ * Any pending underrun events are cleared before unmasking the interrupt to
+ * guarantee that further underrun events are really caused by the new
+ * configuration.
+ */
+static inline void drm_atomic_helper_underrun_start(struct drm_device *dev)
+{
+	atomic_set(&dev->underrun, 0);
+
+	if (dev->mode_config.funcs->atomic_clear_underrun)
+		dev->mode_config.funcs->atomic_clear_underrun(dev);
+
+	if (dev->mode_config.funcs->atomic_unmask_underrun)
+		dev->mode_config.funcs->atomic_unmask_underrun(dev);
+}
+
 #endif /* DRM_ATOMIC_HELPER_H_ */
diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
index 42411b3ea0c8..59c63e03a776 100644
--- a/include/drm/drm_device.h
+++ b/include/drm/drm_device.h
@@ -231,6 +231,21 @@ struct drm_device {
 	 * Set by drm_fb_helper_init() and cleared by drm_fb_helper_fini().
 	 */
 	struct drm_fb_helper *fb_helper;
+
+	/**
+	 * @underrun:
+	 *
+	 * Set to 1 when an underrun error happened since the last atomic
+	 * commit, 0 otherwise.
+	 * This is particularly useful to detect when a specific modeset is too
+	 * demanding in term of memory bandwidth or other kind limitations that
+	 * are hard to guess at atomic check time.
+	 * Drivers should only set underrun to 1 (using
+	 * drm_atomic_helper_underrun_set()) when an underrun interrupt occurs
+	 * and clear if from their atomic commit hook (using
+	 * drm_atomic_helper_underrun_{stop,start}()).
+	 */
+	atomic_t underrun;
 };
 
 #endif
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index 8d540cdae57d..28b83e295fd2 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -325,6 +325,32 @@ struct drm_mode_config_funcs {
 	 * &drm_private_state and &drm_private_obj.
 	 */
 	void (*atomic_state_free)(struct drm_atomic_state *state);
+
+	/**
+	 * @atomic_mask_underrun:
+	 *
+	 * This is an optional hook called when the core needs to disable/mask
+	 * the underrun interrupt. It's used by drm_atomic_helper_underrun_set()
+	 * and drm_atomic_helper_underrun_stop().
+	 */
+	void (*atomic_mask_underrun)(struct drm_device *dev);
+
+	/**
+	 * @atomic_clear_underrun:
+	 *
+	 * This is an optional hook called when the core needs to clear pending
+	 * underrun events. It's used by drm_atomic_helper_underrun_start() to
+	 * before unmask the underrun interrupt.
+	 */
+	void (*atomic_clear_underrun)(struct drm_device *dev);
+
+	/**
+	 * @atomic_unmask_underrun:
+	 *
+	 * This is an optional hook called when the core needs to unmask
+	 * underrun interrupts. It's used by drm_atomic_helper_underrun_start().
+	 */
+	void (*atomic_unmask_underrun)(struct drm_device *dev);
 };
 
 /**
