Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm: kms_helper: don't lose hotplug event

There's a race window (small for hpd, 10s large for polled outputs)
where userspace could sneak in with an unrelated connnector probe
ioctl call and eat the hotplug event (since neither the hpd nor the
poll code see a state change).

To avoid this, check whether the connector state changes in all other
->detect calls (in the current helper code that's only probe_single)
and if that's the case, fire off a hotplug event. Note that we can't
directly call the hotplug event handler, since that expects that no
locks are held (due to reentrancy with the fb code to update the kms
console).

Also, this requires that drivers using the probe_single helper
function set up the poll work. All current drivers do that already,
and with the reworked hpd handling there'll be no downside to
unconditionally setting up the poll work any more.

Reviewed-by: Chris Wilson <chris@chris-wilson.co.uk>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Dave Airlie <airlied@redhat.com>

authored by

Daniel Vetter and committed by
Dave Airlie
160954b7 2b54f781

+32 -1
+31 -1
drivers/gpu/drm/drm_crtc_helper.c
··· 122 122 int count = 0; 123 123 int mode_flags = 0; 124 124 bool verbose_prune = true; 125 + enum drm_connector_status old_status; 125 126 126 127 DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, 127 128 drm_get_connector_name(connector)); ··· 138 137 if (connector->funcs->force) 139 138 connector->funcs->force(connector); 140 139 } else { 140 + old_status = connector->status; 141 + 141 142 connector->status = connector->funcs->detect(connector, true); 143 + 144 + /* 145 + * Normally either the driver's hpd code or the poll loop should 146 + * pick up any changes and fire the hotplug event. But if 147 + * userspace sneaks in a probe, we might miss a change. Hence 148 + * check here, and if anything changed start the hotplug code. 149 + */ 150 + if (old_status != connector->status) { 151 + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %d to %d\n", 152 + connector->base.id, 153 + drm_get_connector_name(connector), 154 + old_status, connector->status); 155 + 156 + /* 157 + * The hotplug event code might call into the fb 158 + * helpers, and so expects that we do not hold any 159 + * locks. Fire up the poll struct instead, it will 160 + * disable itself again. 161 + */ 162 + dev->mode_config.delayed_event = true; 163 + schedule_delayed_work(&dev->mode_config.output_poll_work, 164 + 0); 165 + } 142 166 } 143 167 144 168 /* Re-enable polling in case the global poll config changed. */ ··· 1011 985 struct drm_device *dev = container_of(delayed_work, struct drm_device, mode_config.output_poll_work); 1012 986 struct drm_connector *connector; 1013 987 enum drm_connector_status old_status; 1014 - bool repoll = false, changed = false; 988 + bool repoll = false, changed; 989 + 990 + /* Pick up any changes detected by the probe functions. */ 991 + changed = dev->mode_config.delayed_event; 992 + dev->mode_config.delayed_event = false; 1015 993 1016 994 if (!drm_kms_helper_poll) 1017 995 return;
+1
include/drm/drm_crtc.h
··· 808 808 /* output poll support */ 809 809 bool poll_enabled; 810 810 bool poll_running; 811 + bool delayed_event; 811 812 struct delayed_work output_poll_work; 812 813 813 814 /* pointers to standard properties */