diff mbox series

[2/7] drm/probe-helper: Optionally report single connected output per CRTC

Message ID 20240715093936.793552-3-tzimmermann@suse.de (mailing list archive)
State New, archived
Headers show
Series drm/probe-helpers: Work around multi-outputs-per-CRTC problem | expand

Commit Message

Thomas Zimmermann July 15, 2024, 9:38 a.m. UTC
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@suse.de>
---
 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(+)

Comments

Maxime Ripard July 16, 2024, 9:01 a.m. UTC | #1
Hi,

On Mon, Jul 15, 2024 at 11:38:58AM GMT, Thomas Zimmermann wrote:
> 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.

Do they?

At least the RaspberryPi has multiple connectors on a single CRTC, and
for multiple CRTCs.

My understanding is that it's definitely expected, and any decent
user-space should expect it.

Did you have any bug with this?

And if it was the case, we wouldn't support cloning at all. I couldn't
really find how cloning works exactly, but my understanding was that
it's the difference between drm_encoder.possible_crtcs and
possible_clones: possible_crtcs lists the CRTCs it can be connected to,
and possible_clones the other encoders that can run with in parallel.

> 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@suse.de>

The whole "is it actually needed" discussion aside, I'm not sure it's a
good idea to use a config option for that. Chances are distros will
either enable it or disable it depending on what they/their customers
workload will typically look like, and it won't make the kernel or
compositor's job any easier.

Could we use a client capability for that maybe?

Maxime
Daniel Vetter July 16, 2024, 9:46 a.m. UTC | #2
On Mon, Jul 15, 2024 at 11:38:58AM +0200, Thomas Zimmermann wrote:
> 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@suse.de>
> ---
>  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".

Uh I think this is way too much, because this defacto makes this uapi for
all drivers, forever.

The reason we added the hacks for the bmc connectors was the old "no
regressions" rule: Adding the BMC connectors broke the setup for existing
users, we can't have that, hence why the hack was needed. For any new
driver, or for new platforms, we don't have this regression problem.

So I think the better way to lift this code from ast/mga is if we a lot
more focused workaround:

- Add a new probe helper for subordinate connectors, they will report
  disconnected if any other connector is connected.

- Put a really big warning onto that function that it should only be used
  as a workaround for the regression case, not anywhere else.

- Ideally drivers also don't use that for any new chips where the "no
  regression" rule doesn't apply.

- I wouldn't bother with the Kconfig, because if we make it a global
  option we cannot ever change it anyway. The only way to phase this out
  is by never applying this hack to new hardware support.

I think it would be also good to link to the specific userspace that falls
over, and how it falls over. At least hunting around in git history for
ast/mga200 didn't reveal anything.

Cheers, Sima
> +
>  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
>
Thomas Zimmermann July 16, 2024, 10:37 a.m. UTC | #3
Hi

Am 16.07.24 um 11:46 schrieb Daniel Vetter:
> On Mon, Jul 15, 2024 at 11:38:58AM +0200, Thomas Zimmermann wrote:
>> 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@suse.de>
>> ---
>>   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".
> Uh I think this is way too much, because this defacto makes this uapi for
> all drivers, forever.
>
> The reason we added the hacks for the bmc connectors was the old "no
> regressions" rule: Adding the BMC connectors broke the setup for existing
> users, we can't have that, hence why the hack was needed. For any new
> driver, or for new platforms, we don't have this regression problem.

It's a problem with userspace, not with drivers. The ast and mgag200 
drivers would be fixed easily.

Wrt UAPI, drivers have to opt-in by setting the prioritized_connectors 
bitmask. Anything but ast and mgag200 will not be affected in any case. 
And for ast/mgag200 there's also no change from the current behavior.

>
> So I think the better way to lift this code from ast/mga is if we a lot
> more focused workaround:
>
> - Add a new probe helper for subordinate connectors, they will report
>    disconnected if any other connector is connected.

There are no subordinate connectors. They are all equal. The current 
patch does what you suggest, but in a generic fashion. I'd otherwise 
have to introduce more abstraction to each affected driver to 
differentiate between the reported status and the real status.

>
> - Put a really big warning onto that function that it should only be used
>    as a workaround for the regression case, not anywhere else.

Isn't that what the patch already does in some way?

>
> - Ideally drivers also don't use that for any new chips where the "no
>    regression" rule doesn't apply.
>
> - I wouldn't bother with the Kconfig, because if we make it a global
>    option we cannot ever change it anyway. The only way to phase this out
>    is by never applying this hack to new hardware support.

I liked Maxime's idea of providing a client cap for userspace that is 
not affected.

I don't think that current userspace treats ast and mgag200 any 
different from the other drivers. It's just that userspace doesn't 
support these drivers' hardware layout. If we add a new driver for new 
hardware with similar limitations, it would also be affected. I think 
that userspace would end up in a whack-a-mole situation if they start 
treating such hardware specifically.

The real fix here is userspace supporting (or at least actively 
ignoring) multiple connected outputs per CRTC.

>
> I think it would be also good to link to the specific userspace that falls
> over, and how it falls over. At least hunting around in git history for
> ast/mga200 didn't reveal anything.

AFAIU it's most of current userspace. At least Gnome, but also KDE and 
Weston.

Best regards
Thomas

>
> Cheers, Sima
>> +
>>   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
>>
Thomas Zimmermann July 16, 2024, 10:47 a.m. UTC | #4
Hi

Am 16.07.24 um 11:01 schrieb Maxime Ripard:
> Hi,
>
> On Mon, Jul 15, 2024 at 11:38:58AM GMT, Thomas Zimmermann wrote:
>> 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.
> Do they?

That was my understanding from discussions with Gnome devs. Or at least, 
it's what they officially support.

>
> At least the RaspberryPi has multiple connectors on a single CRTC, and
> for multiple CRTCs.

I think userspace wants at least one CRTC per output, but doesn't care 
about details.

>
> My understanding is that it's definitely expected, and any decent
> user-space should expect it.
>
> Did you have any bug with this?

https://gitlab.gnome.org/GNOME/mutter/-/issues/3157

There was also a lengthy discussion on IRC a few months ago IIRC.

> And if it was the case, we wouldn't support cloning at all. I couldn't
> really find how cloning works exactly, but my understanding was that
> it's the difference between drm_encoder.possible_crtcs and
> possible_clones: possible_crtcs lists the CRTCs it can be connected to,
> and possible_clones the other encoders that can run with in parallel.

I'd prefer to solve this with the possible_clones field. But it didn't 
work. Any ideas on that?

>
>> 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@suse.de>
> The whole "is it actually needed" discussion aside, I'm not sure it's a
> good idea to use a config option for that. Chances are distros will
> either enable it or disable it depending on what they/their customers
> workload will typically look like, and it won't make the kernel or
> compositor's job any easier.
>
> Could we use a client capability for that maybe?

I like this idea.

Best regards
Thomas

>
> Maxime
diff mbox series

Patch

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);