For CRTCs with multiple outputs (i.e., encoders plus connectors), only report at most a single connected output to userspace. Make this configurable via CONFIG_DRM_REPORT_SINGLE_CONNECTED_CRTC_OUTPUT. Having multiple connected outputs on the same CRTC complicates display-mode and format selection, so most userspace does not support this. This is mostly not a problem in practice, as modern display hardware provides a separate CRTC for each output. On old hardware or hardware with BMCs, a single CRTC often drives multiple displays. Only reporting one of them as connected makes the hardware compatible with common userspace. Add the field prioritized_connectors to struct drm_connector. The bitmask signals which other connectors have priority. Also provide the helper drm_probe_helper_prioritize_connectors() that sets default priorities for a given set of connectors. Calling the helper should be enough to set up the functionality for most drivers. With the prioritization bits in place, update connector-status detection to test against prioritized conenctors. So when the probe helpers detect a connector as connected, test against the prioritized connectors. If any is also connected, set the connector status to disconnected. Please note that this functionality is a workaround for limitations in userspace. If your compositor supports multiple outputs per CRTC, CONFIG_DRM_REPORT_SINGLE_CONNECTED_CRTC_OUTPUT should be disabled. Signed-off-by: Thomas Zimmermann <tzimmermann@xxxxxxx> --- drivers/gpu/drm/Kconfig | 15 +++++ drivers/gpu/drm/drm_probe_helper.c | 104 +++++++++++++++++++++++++++++ include/drm/drm_connector.h | 2 + include/drm/drm_probe_helper.h | 2 + 4 files changed, 123 insertions(+) diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index fd0749c0c630..d1afdbd2d93b 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -105,6 +105,21 @@ config DRM_KMS_HELPER help CRTC helpers for KMS drivers. +config DRM_REPORT_SINGLE_CONNECTED_CRTC_OUTPUT + bool "Report only a single connected output per CRTC" + depends on DRM + default n + help + CRTCs can support multiple encoders and connectors for output. + More than one pair can be connected to a display at a time. Most + userspace only supports at most one connected output per CRTC at a + time. Enable this option to let DRM report at most one connected + output per CRTC. This is mostly relevant for low-end and old + hardware. Most modern graphics hardware supports a separate CRTC + per output and won't be affected by this setting. + + If in doubt, say "Y". + config DRM_PANIC bool "Display a user-friendly message when a kernel panic occurs" depends on DRM && !(FRAMEBUFFER_CONSOLE && VT_CONSOLE) diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index f14301abf53f..fc0652635148 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -352,6 +352,74 @@ static int detect_connector_status(struct drm_connector *connector, return connector_status_connected; } +static int reported_connector_status(struct drm_connector *connector, int detected_status, + struct drm_modeset_acquire_ctx *ctx, bool force) +{ +#if defined(CONFIG_DRM_REPORT_SINGLE_CONNECTED_CRTC_OUTPUT) + struct drm_connector *prio_connector = connector; + struct drm_device *dev = connector->dev; + struct drm_connector_list_iter iter; + struct drm_connector *pos; + u32 connector_mask; + int ret = 0; + + if (!connector->prioritized_connectors) + return detected_status; + + if (detected_status != connector_status_connected) + return detected_status; + + connector_mask = drm_connector_mask(connector); + + /* + * Find the connector with status 'connected' and a higher + * priority. + */ + drm_connector_list_iter_begin(dev, &iter); + drm_for_each_connector_iter(pos, &iter) { + if (!(drm_connector_mask(pos) & connector->prioritized_connectors)) + continue; + + /* + * Warn if connector has priority over itself. + */ + if (drm_WARN_ON_ONCE(dev, pos == connector)) + continue; + + /* + * Warn if both connectors have priority over each other. Pick the + * one with the lower index. + */ + if (drm_WARN_ON_ONCE(dev, pos->prioritized_connectors & connector_mask)) { + if (pos->index > connector->index) + continue; + } + + ret = detect_connector_status(pos, ctx, force); + if (ret < 0) + break; + if (ret == connector_status_disconnected) + continue; + + prio_connector = pos; + break; + } + drm_connector_list_iter_end(&iter); + + if (ret < 0) + return ret; + + /* + * We've found another connected connector. Report our connector + * as 'disconnected'. + */ + if (prio_connector != connector) + detected_status = connector_status_disconnected; +#endif + + return detected_status; +} + static enum drm_connector_status drm_helper_probe_detect_ctx(struct drm_connector *connector, bool force) { @@ -373,6 +441,12 @@ drm_helper_probe_detect_ctx(struct drm_connector *connector, bool force) if (WARN_ON(ret < 0)) ret = connector_status_unknown; + ret = reported_connector_status(connector, ret, &ctx, force); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry; + } + if (ret != connector->status) connector->epoch_counter += 1; @@ -408,6 +482,7 @@ drm_helper_probe_detect(struct drm_connector *connector, return ret; ret = detect_connector_status(connector, ctx, force); + ret = reported_connector_status(connector, ret, ctx, force); if (ret != connector->status) connector->epoch_counter += 1; @@ -416,6 +491,35 @@ drm_helper_probe_detect(struct drm_connector *connector, } EXPORT_SYMBOL(drm_helper_probe_detect); +/** + * drm_probe_helper_prioritize_connectors - Set connector priorities + * @dev: the DRM device with connectors + * @connector_mask: Bitmask connector indices + * + * drm_probe_helper_prioritize_connectors() prioritizes all connectors + * specified in @connector_mask. All given connectors are assumed to + * interfere with each other. Connectors with a lower index have priority + * over connectors with a higher index. + */ +void drm_probe_helper_prioritize_connectors(struct drm_device *dev, u32 connector_mask) +{ + struct drm_connector_list_iter iter; + struct drm_connector *connector; + u32 prioritized_connectors = 0; + + drm_connector_list_iter_begin(dev, &iter); + drm_for_each_connector_iter(connector, &iter) { + u32 mask = drm_connector_mask(connector); + + if (!(mask & connector_mask)) + continue; + connector->prioritized_connectors = prioritized_connectors; + prioritized_connectors |= mask; + } + drm_connector_list_iter_end(&iter); +} +EXPORT_SYMBOL(drm_probe_helper_prioritize_connectors); + static int drm_helper_probe_get_modes(struct drm_connector *connector) { const struct drm_connector_helper_funcs *connector_funcs = diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 5ad735253413..e3039478e928 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1985,6 +1985,8 @@ struct drm_connector { /** @epoch_counter: used to detect any other changes in connector, besides status */ u64 epoch_counter; + u32 prioritized_connectors; + /** * @possible_encoders: Bit mask of encoders that can drive this * connector, drm_encoder_index() determines the index into the bitfield diff --git a/include/drm/drm_probe_helper.h b/include/drm/drm_probe_helper.h index d6ce7b218b77..05e23485550d 100644 --- a/include/drm/drm_probe_helper.h +++ b/include/drm/drm_probe_helper.h @@ -17,6 +17,8 @@ int drm_helper_probe_detect(struct drm_connector *connector, struct drm_modeset_acquire_ctx *ctx, bool force); +void drm_probe_helper_prioritize_connectors(struct drm_device *dev, u32 connector_mask); + int drmm_kms_helper_poll_init(struct drm_device *dev); void drm_kms_helper_poll_init(struct drm_device *dev); void drm_kms_helper_poll_fini(struct drm_device *dev); -- 2.45.2