diff mbox series

[4/8] drm/client: Make copies of modes

Message ID 20241003113304.11700-5-ville.syrjala@linux.intel.com (mailing list archive)
State New, archived
Headers show
Series drm/client: Stop using legacy crtc->mode and a bunch of cleanups | expand

Commit Message

Ville Syrjälä Oct. 3, 2024, 11:33 a.m. UTC
From: Ville Syrjälä <ville.syrjala@linux.intel.com>

drm_client_firmware_config() is currently picking up the current
mode of the crtc via the legacy crtc->mode, which is not supposed
to be used by atomic drivers at all. We can't simply switch over
to the proper crtc->state->mode because we drop the crtc->mutex
(which protects crtc->state) before the mode gets used.

The most straightforward solution to extend the lifetime of
modes[] seem to be to make full copies of the modes instead
of just storing pointers. We do have to replace the NULL checks
with something else though. Checking that mode->clock!=0
should be sufficient.

And with this we can undo also commit 3eadd887dbac
("drm/client:Fully protect modes[] with dev->mode_config.mutex")
as the lifetime of modes[] no longer has anything to do with
that lock.

Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
---
 drivers/gpu/drm/drm_client_modeset.c | 80 +++++++++++++++-------------
 1 file changed, 43 insertions(+), 37 deletions(-)

Comments

Ville Syrjälä Oct. 3, 2024, 4:45 p.m. UTC | #1
On Thu, Oct 03, 2024 at 02:33:00PM +0300, Ville Syrjala wrote:
> From: Ville Syrjälä <ville.syrjala@linux.intel.com>
> 
> drm_client_firmware_config() is currently picking up the current
> mode of the crtc via the legacy crtc->mode, which is not supposed
> to be used by atomic drivers at all. We can't simply switch over
> to the proper crtc->state->mode because we drop the crtc->mutex
> (which protects crtc->state) before the mode gets used.
> 
> The most straightforward solution to extend the lifetime of
> modes[] seem to be to make full copies of the modes instead
> of just storing pointers. We do have to replace the NULL checks
> with something else though. Checking that mode->clock!=0
> should be sufficient.
> 
> And with this we can undo also commit 3eadd887dbac
> ("drm/client:Fully protect modes[] with dev->mode_config.mutex")
> as the lifetime of modes[] no longer has anything to do with
> that lock.
> 
> Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
> ---
>  drivers/gpu/drm/drm_client_modeset.c | 80 +++++++++++++++-------------
>  1 file changed, 43 insertions(+), 37 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
> index 888323137a6a..d413e119db3f 100644
> --- a/drivers/gpu/drm/drm_client_modeset.c
> +++ b/drivers/gpu/drm/drm_client_modeset.c
> @@ -265,10 +265,15 @@ static void drm_client_connectors_enabled(struct drm_connector *connectors[],
>  		enabled[i] = drm_connector_enabled(connectors[i], false);
>  }
>  
> +static bool mode_valid(const struct drm_display_mode *mode)
> +{
> +	return mode->clock != 0;
> +}
> +
>  static bool drm_client_target_cloned(struct drm_device *dev,
>  				     struct drm_connector *connectors[],
>  				     unsigned int connector_count,
> -				     const struct drm_display_mode *modes[],
> +				     struct drm_display_mode modes[],
>  				     struct drm_client_offset offsets[],
>  				     bool enabled[], int width, int height)
>  {
> @@ -296,15 +301,16 @@ static bool drm_client_target_cloned(struct drm_device *dev,
>  	for (i = 0; i < connector_count; i++) {
>  		if (!enabled[i])
>  			continue;
> -		modes[i] = drm_connector_pick_cmdline_mode(connectors[i]);
> -		if (!modes[i]) {
> +
> +		drm_mode_copy(&modes[i], drm_connector_pick_cmdline_mode(connectors[i]));

Apparently this guy doesn't accept NULL, so this is oops central now.
I'll have to wrap these up a bit I guess.

> +		if (!mode_valid(&modes[i])) {
>  			can_clone = false;
>  			break;
>  		}
>  		for (j = 0; j < i; j++) {
>  			if (!enabled[j])
>  				continue;
> -			if (!drm_mode_match(modes[j], modes[i],
> +			if (!drm_mode_match(&modes[j], &modes[i],
>  					    DRM_MODE_MATCH_TIMINGS |
>  					    DRM_MODE_MATCH_CLOCK |
>  					    DRM_MODE_MATCH_FLAGS |
> @@ -335,9 +341,9 @@ static bool drm_client_target_cloned(struct drm_device *dev,
>  					   DRM_MODE_MATCH_CLOCK |
>  					   DRM_MODE_MATCH_FLAGS |
>  					   DRM_MODE_MATCH_3D_FLAGS))
> -				modes[i] = mode;
> +				drm_mode_copy(&modes[i], mode);
>  		}
> -		if (!modes[i])
> +		if (!mode_valid(&modes[i]))
>  			can_clone = false;
>  	}
>  	drm_mode_destroy(dev, dmt_mode);
> @@ -354,7 +360,7 @@ static bool drm_client_target_cloned(struct drm_device *dev,
>  static int drm_client_get_tile_offsets(struct drm_device *dev,
>  				       struct drm_connector *connectors[],
>  				       unsigned int connector_count,
> -				       const struct drm_display_mode *modes[],
> +				       const struct drm_display_mode modes[],
>  				       struct drm_client_offset offsets[],
>  				       int idx,
>  				       int h_idx, int v_idx)
> @@ -368,17 +374,17 @@ static int drm_client_get_tile_offsets(struct drm_device *dev,
>  		if (!connector->has_tile)
>  			continue;
>  
> -		if (!modes[i] && (h_idx || v_idx)) {
> +		if (!mode_valid(&modes[i]) && (h_idx || v_idx)) {
>  			drm_dbg_kms(dev,
>  				    "[CONNECTOR:%d:%s] no modes for connector tiled %d\n",
>  				    connector->base.id, connector->name, i);
>  			continue;
>  		}
>  		if (connector->tile_h_loc < h_idx)
> -			hoffset += modes[i]->hdisplay;
> +			hoffset += modes[i].hdisplay;
>  
>  		if (connector->tile_v_loc < v_idx)
> -			voffset += modes[i]->vdisplay;
> +			voffset += modes[i].vdisplay;
>  	}
>  	offsets[idx].x = hoffset;
>  	offsets[idx].y = voffset;
> @@ -389,7 +395,7 @@ static int drm_client_get_tile_offsets(struct drm_device *dev,
>  static bool drm_client_target_preferred(struct drm_device *dev,
>  					struct drm_connector *connectors[],
>  					unsigned int connector_count,
> -					const struct drm_display_mode *modes[],
> +					struct drm_display_mode modes[],
>  					struct drm_client_offset offsets[],
>  					bool enabled[], int width, int height)
>  {
> @@ -445,16 +451,16 @@ static bool drm_client_target_preferred(struct drm_device *dev,
>  		}
>  
>  		mode_type = "cmdline";
> -		modes[i] = drm_connector_pick_cmdline_mode(connector);
> +		drm_mode_copy(&modes[i], drm_connector_pick_cmdline_mode(connector));
>  
> -		if (!modes[i]) {
> +		if (!mode_valid(&modes[i])) {
>  			mode_type = "preferred";
> -			modes[i] = drm_connector_preferred_mode(connector, width, height);
> +			drm_mode_copy(&modes[i], drm_connector_preferred_mode(connector, width, height));
>  		}
>  
> -		if (!modes[i]) {
> +		if (!mode_valid(&modes[i])) {
>  			mode_type = "first";
> -			modes[i] = drm_connector_first_mode(connector);
> +			drm_mode_copy(&modes[i], drm_connector_first_mode(connector));
>  		}
>  
>  		/*
> @@ -472,17 +478,17 @@ static bool drm_client_target_preferred(struct drm_device *dev,
>  			     connector->tile_v_loc == 0 &&
>  			     !drm_connector_get_tiled_mode(connector))) {
>  				mode_type = "non tiled";
> -				modes[i] = drm_connector_fallback_non_tiled_mode(connector);
> +				drm_mode_copy(&modes[i], drm_connector_fallback_non_tiled_mode(connector));
>  			} else {
>  				mode_type = "tiled";
> -				modes[i] = drm_connector_get_tiled_mode(connector);
> +				drm_mode_copy(&modes[i], drm_connector_get_tiled_mode(connector));
>  			}
>  		}
>  
> -		if (modes[i])
> +		if (mode_valid(&modes[i]))
>  			drm_dbg_kms(dev, "[CONNECTOR:%d:%s] found %s mode: %s\n",
>  				    connector->base.id, connector->name,
> -				    mode_type, modes[i]->name);
> +				    mode_type, modes[i].name);
>  		else
>  			drm_dbg_kms(dev, "[CONNECTOR:%d:%s] no mode found\n",
>  				    connector->base.id, connector->name);
> @@ -514,7 +520,7 @@ static int drm_client_pick_crtcs(struct drm_client_dev *client,
>  				 struct drm_connector *connectors[],
>  				 unsigned int connector_count,
>  				 struct drm_crtc *best_crtcs[],
> -				 const struct drm_display_mode *modes[],
> +				 const struct drm_display_mode modes[],
>  				 int n, int width, int height)
>  {
>  	struct drm_device *dev = client->dev;
> @@ -532,7 +538,7 @@ static int drm_client_pick_crtcs(struct drm_client_dev *client,
>  	best_crtcs[n] = NULL;
>  	best_score = drm_client_pick_crtcs(client, connectors, connector_count,
>  					   best_crtcs, modes, n + 1, width, height);
> -	if (modes[n] == NULL)
> +	if (!mode_valid(&modes[n]))
>  		return best_score;
>  
>  	crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL);
> @@ -566,7 +572,7 @@ static int drm_client_pick_crtcs(struct drm_client_dev *client,
>  			if (dev->mode_config.num_crtc > 1)
>  				continue;
>  
> -			if (!drm_mode_equal(modes[o], modes[n]))
> +			if (!drm_mode_equal(&modes[o], &modes[n]))
>  				continue;
>  		}
>  
> @@ -589,7 +595,7 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
>  				       struct drm_connector *connectors[],
>  				       unsigned int connector_count,
>  				       struct drm_crtc *crtcs[],
> -				       const struct drm_display_mode *modes[],
> +				       struct drm_display_mode modes[],
>  				       struct drm_client_offset offsets[],
>  				       bool enabled[], int width, int height)
>  {
> @@ -690,20 +696,20 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
>  		}
>  
>  		mode_type = "cmdline";
> -		modes[i] = drm_connector_pick_cmdline_mode(connector);
> +		drm_mode_copy(&modes[i], drm_connector_pick_cmdline_mode(connector));
>  
> -		if (!modes[i]) {
> +		if (!mode_valid(&modes[i])) {
>  			mode_type = "preferred";
> -			modes[i] = drm_connector_preferred_mode(connector, width, height);
> +			drm_mode_copy(&modes[i], drm_connector_preferred_mode(connector, width, height));
>  		}
>  
> -		if (!modes[i]) {
> +		if (!mode_valid(&modes[i])) {
>  			mode_type = "first";
> -			modes[i] = drm_connector_first_mode(connector);
> +			drm_mode_copy(&modes[i], drm_connector_first_mode(connector));
>  		}
>  
>  		/* last resort: use current mode */
> -		if (!modes[i]) {
> +		if (!mode_valid(&modes[i])) {
>  			/*
>  			 * IMPORTANT: We want to use the adjusted mode (i.e.
>  			 * after the panel fitter upscaling) as the initial
> @@ -716,7 +722,7 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
>  			 * fastboot check to work correctly.
>  			 */
>  			mode_type = "current";
> -			modes[i] = &connector->state->crtc->mode;
> +			drm_mode_copy(&modes[i], &connector->state->crtc->mode);
>  		}
>  
>  		/*
> @@ -726,14 +732,14 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
>  		if (connector->has_tile &&
>  		    num_tiled_conns < connector->num_h_tile * connector->num_v_tile) {
>  			mode_type = "non tiled";
> -			modes[i] = drm_connector_fallback_non_tiled_mode(connector);
> +			drm_mode_copy(&modes[i], drm_connector_fallback_non_tiled_mode(connector));
>  		}
>  		crtcs[i] = new_crtc;
>  
>  		drm_dbg_kms(dev, "[CONNECTOR::%d:%s] on [CRTC:%d:%s] using %s mode: %s\n",
>  			    connector->base.id, connector->name,
>  			    new_crtc->base.id, new_crtc->name,
> -			    mode_type, modes[i]->name);
> +			    mode_type, modes[i].name);
>  
>  		fallback = false;
>  		conn_configured |= BIT(i);
> @@ -789,8 +795,7 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
>  	unsigned int total_modes_count = 0;
>  	struct drm_client_offset *offsets;
>  	unsigned int connector_count = 0;
> -	/* points to modes protected by mode_config.mutex */
> -	const struct drm_display_mode **modes;
> +	struct drm_display_mode *modes;
>  	struct drm_crtc **crtcs;
>  	int i, ret = 0;
>  	bool *enabled;
> @@ -858,10 +863,12 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
>  				      crtcs, modes, 0, width, height);
>  	}
>  
> +	mutex_unlock(&dev->mode_config.mutex);
> +
>  	drm_client_modeset_release(client);
>  
>  	for (i = 0; i < connector_count; i++) {
> -		const struct drm_display_mode *mode = modes[i];
> +		const struct drm_display_mode *mode = &modes[i];
>  		struct drm_crtc *crtc = crtcs[i];
>  		struct drm_client_offset *offset = &offsets[i];
>  
> @@ -892,7 +899,6 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
>  			modeset->y = offset->y;
>  		}
>  	}
> -	mutex_unlock(&dev->mode_config.mutex);
>  
>  	mutex_unlock(&client->modeset_mutex);
>  out:
> -- 
> 2.45.2
Thomas Zimmermann Oct. 7, 2024, 7:36 a.m. UTC | #2
Hi

Am 03.10.24 um 13:33 schrieb Ville Syrjala:
> From: Ville Syrjälä <ville.syrjala@linux.intel.com>
>
> drm_client_firmware_config() is currently picking up the current
> mode of the crtc via the legacy crtc->mode, which is not supposed
> to be used by atomic drivers at all. We can't simply switch over
> to the proper crtc->state->mode because we drop the crtc->mutex
> (which protects crtc->state) before the mode gets used.
>
> The most straightforward solution to extend the lifetime of
> modes[] seem to be to make full copies of the modes instead
> of just storing pointers. We do have to replace the NULL checks
> with something else though. Checking that mode->clock!=0
> should be sufficient.
>
> And with this we can undo also commit 3eadd887dbac
> ("drm/client:Fully protect modes[] with dev->mode_config.mutex")
> as the lifetime of modes[] no longer has anything to do with
> that lock.

I think it would be a lot better to first build that mode list while 
holding the mutex, and afterwards copy the resulting modes before 
releasing the lock. The code below is convoluted with drm_mode_copy().

>
> Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
> ---
>   drivers/gpu/drm/drm_client_modeset.c | 80 +++++++++++++++-------------
>   1 file changed, 43 insertions(+), 37 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
> index 888323137a6a..d413e119db3f 100644
> --- a/drivers/gpu/drm/drm_client_modeset.c
> +++ b/drivers/gpu/drm/drm_client_modeset.c
> @@ -265,10 +265,15 @@ static void drm_client_connectors_enabled(struct drm_connector *connectors[],
>   		enabled[i] = drm_connector_enabled(connectors[i], false);
>   }
>   
> +static bool mode_valid(const struct drm_display_mode *mode)
> +{
> +	return mode->clock != 0;

A mode's clock isn't always known and not all drivers might set it 
correctly. At least in simpledrm/ofdrm, we have to make up a clock value 
for the firmware framebuffer. Otherwise some of our userspace would oops.

The test for clock != 0 makes sense, but it's maybe the wrong place to 
do this. Would a test for hdisplay/vdisplay != 0 work instead?

> +}
> +
>   static bool drm_client_target_cloned(struct drm_device *dev,
>   				     struct drm_connector *connectors[],
>   				     unsigned int connector_count,
> -				     const struct drm_display_mode *modes[],
> +				     struct drm_display_mode modes[],
>   				     struct drm_client_offset offsets[],
>   				     bool enabled[], int width, int height)
>   {
> @@ -296,15 +301,16 @@ static bool drm_client_target_cloned(struct drm_device *dev,
>   	for (i = 0; i < connector_count; i++) {
>   		if (!enabled[i])
>   			continue;
> -		modes[i] = drm_connector_pick_cmdline_mode(connectors[i]);
> -		if (!modes[i]) {
> +
> +		drm_mode_copy(&modes[i], drm_connector_pick_cmdline_mode(connectors[i]));
> +		if (!mode_valid(&modes[i])) {

You're copying and only then test for validity?

>   			can_clone = false;
>   			break;
>   		}
>   		for (j = 0; j < i; j++) {
>   			if (!enabled[j])
>   				continue;
> -			if (!drm_mode_match(modes[j], modes[i],
> +			if (!drm_mode_match(&modes[j], &modes[i],
>   					    DRM_MODE_MATCH_TIMINGS |
>   					    DRM_MODE_MATCH_CLOCK |
>   					    DRM_MODE_MATCH_FLAGS |
> @@ -335,9 +341,9 @@ static bool drm_client_target_cloned(struct drm_device *dev,
>   					   DRM_MODE_MATCH_CLOCK |
>   					   DRM_MODE_MATCH_FLAGS |
>   					   DRM_MODE_MATCH_3D_FLAGS))
> -				modes[i] = mode;
> +				drm_mode_copy(&modes[i], mode);
>   		}
> -		if (!modes[i])
> +		if (!mode_valid(&modes[i]))
>   			can_clone = false;
>   	}
>   	drm_mode_destroy(dev, dmt_mode);
> @@ -354,7 +360,7 @@ static bool drm_client_target_cloned(struct drm_device *dev,
>   static int drm_client_get_tile_offsets(struct drm_device *dev,
>   				       struct drm_connector *connectors[],
>   				       unsigned int connector_count,
> -				       const struct drm_display_mode *modes[],
> +				       const struct drm_display_mode modes[],
>   				       struct drm_client_offset offsets[],
>   				       int idx,
>   				       int h_idx, int v_idx)
> @@ -368,17 +374,17 @@ static int drm_client_get_tile_offsets(struct drm_device *dev,
>   		if (!connector->has_tile)
>   			continue;
>   
> -		if (!modes[i] && (h_idx || v_idx)) {
> +		if (!mode_valid(&modes[i]) && (h_idx || v_idx)) {
>   			drm_dbg_kms(dev,
>   				    "[CONNECTOR:%d:%s] no modes for connector tiled %d\n",
>   				    connector->base.id, connector->name, i);
>   			continue;
>   		}
>   		if (connector->tile_h_loc < h_idx)
> -			hoffset += modes[i]->hdisplay;
> +			hoffset += modes[i].hdisplay;
>   
>   		if (connector->tile_v_loc < v_idx)
> -			voffset += modes[i]->vdisplay;
> +			voffset += modes[i].vdisplay;
>   	}
>   	offsets[idx].x = hoffset;
>   	offsets[idx].y = voffset;
> @@ -389,7 +395,7 @@ static int drm_client_get_tile_offsets(struct drm_device *dev,
>   static bool drm_client_target_preferred(struct drm_device *dev,
>   					struct drm_connector *connectors[],
>   					unsigned int connector_count,
> -					const struct drm_display_mode *modes[],
> +					struct drm_display_mode modes[],
>   					struct drm_client_offset offsets[],
>   					bool enabled[], int width, int height)
>   {
> @@ -445,16 +451,16 @@ static bool drm_client_target_preferred(struct drm_device *dev,
>   		}
>   
>   		mode_type = "cmdline";
> -		modes[i] = drm_connector_pick_cmdline_mode(connector);
> +		drm_mode_copy(&modes[i], drm_connector_pick_cmdline_mode(connector));
>   
> -		if (!modes[i]) {
> +		if (!mode_valid(&modes[i])) {
>   			mode_type = "preferred";
> -			modes[i] = drm_connector_preferred_mode(connector, width, height);
> +			drm_mode_copy(&modes[i], drm_connector_preferred_mode(connector, width, height));
>   		}
>   
> -		if (!modes[i]) {
> +		if (!mode_valid(&modes[i])) {
>   			mode_type = "first";
> -			modes[i] = drm_connector_first_mode(connector);
> +			drm_mode_copy(&modes[i], drm_connector_first_mode(connector));
>   		}
>   
>   		/*
> @@ -472,17 +478,17 @@ static bool drm_client_target_preferred(struct drm_device *dev,
>   			     connector->tile_v_loc == 0 &&
>   			     !drm_connector_get_tiled_mode(connector))) {
>   				mode_type = "non tiled";
> -				modes[i] = drm_connector_fallback_non_tiled_mode(connector);
> +				drm_mode_copy(&modes[i], drm_connector_fallback_non_tiled_mode(connector));
>   			} else {
>   				mode_type = "tiled";
> -				modes[i] = drm_connector_get_tiled_mode(connector);
> +				drm_mode_copy(&modes[i], drm_connector_get_tiled_mode(connector));
>   			}
>   		}
>   
> -		if (modes[i])
> +		if (mode_valid(&modes[i]))
>   			drm_dbg_kms(dev, "[CONNECTOR:%d:%s] found %s mode: %s\n",
>   				    connector->base.id, connector->name,
> -				    mode_type, modes[i]->name);
> +				    mode_type, modes[i].name);
>   		else
>   			drm_dbg_kms(dev, "[CONNECTOR:%d:%s] no mode found\n",
>   				    connector->base.id, connector->name);
> @@ -514,7 +520,7 @@ static int drm_client_pick_crtcs(struct drm_client_dev *client,
>   				 struct drm_connector *connectors[],
>   				 unsigned int connector_count,
>   				 struct drm_crtc *best_crtcs[],
> -				 const struct drm_display_mode *modes[],
> +				 const struct drm_display_mode modes[],
>   				 int n, int width, int height)
>   {
>   	struct drm_device *dev = client->dev;
> @@ -532,7 +538,7 @@ static int drm_client_pick_crtcs(struct drm_client_dev *client,
>   	best_crtcs[n] = NULL;
>   	best_score = drm_client_pick_crtcs(client, connectors, connector_count,
>   					   best_crtcs, modes, n + 1, width, height);
> -	if (modes[n] == NULL)
> +	if (!mode_valid(&modes[n]))
>   		return best_score;
>   
>   	crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL);
> @@ -566,7 +572,7 @@ static int drm_client_pick_crtcs(struct drm_client_dev *client,
>   			if (dev->mode_config.num_crtc > 1)
>   				continue;
>   
> -			if (!drm_mode_equal(modes[o], modes[n]))
> +			if (!drm_mode_equal(&modes[o], &modes[n]))
>   				continue;
>   		}
>   
> @@ -589,7 +595,7 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
>   				       struct drm_connector *connectors[],
>   				       unsigned int connector_count,
>   				       struct drm_crtc *crtcs[],
> -				       const struct drm_display_mode *modes[],
> +				       struct drm_display_mode modes[],
>   				       struct drm_client_offset offsets[],
>   				       bool enabled[], int width, int height)
>   {
> @@ -690,20 +696,20 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
>   		}
>   
>   		mode_type = "cmdline";
> -		modes[i] = drm_connector_pick_cmdline_mode(connector);
> +		drm_mode_copy(&modes[i], drm_connector_pick_cmdline_mode(connector));
>   
> -		if (!modes[i]) {
> +		if (!mode_valid(&modes[i])) {
>   			mode_type = "preferred";
> -			modes[i] = drm_connector_preferred_mode(connector, width, height);
> +			drm_mode_copy(&modes[i], drm_connector_preferred_mode(connector, width, height));
>   		}
>   
> -		if (!modes[i]) {
> +		if (!mode_valid(&modes[i])) {
>   			mode_type = "first";
> -			modes[i] = drm_connector_first_mode(connector);
> +			drm_mode_copy(&modes[i], drm_connector_first_mode(connector));
>   		}
>   
>   		/* last resort: use current mode */
> -		if (!modes[i]) {
> +		if (!mode_valid(&modes[i])) {
>   			/*
>   			 * IMPORTANT: We want to use the adjusted mode (i.e.
>   			 * after the panel fitter upscaling) as the initial
> @@ -716,7 +722,7 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
>   			 * fastboot check to work correctly.
>   			 */
>   			mode_type = "current";
> -			modes[i] = &connector->state->crtc->mode;
> +			drm_mode_copy(&modes[i], &connector->state->crtc->mode);
>   		}
>   
>   		/*
> @@ -726,14 +732,14 @@ static bool drm_client_firmware_config(struct drm_client_dev *client,
>   		if (connector->has_tile &&
>   		    num_tiled_conns < connector->num_h_tile * connector->num_v_tile) {
>   			mode_type = "non tiled";
> -			modes[i] = drm_connector_fallback_non_tiled_mode(connector);
> +			drm_mode_copy(&modes[i], drm_connector_fallback_non_tiled_mode(connector));
>   		}
>   		crtcs[i] = new_crtc;
>   
>   		drm_dbg_kms(dev, "[CONNECTOR::%d:%s] on [CRTC:%d:%s] using %s mode: %s\n",
>   			    connector->base.id, connector->name,
>   			    new_crtc->base.id, new_crtc->name,
> -			    mode_type, modes[i]->name);
> +			    mode_type, modes[i].name);
>   
>   		fallback = false;
>   		conn_configured |= BIT(i);
> @@ -789,8 +795,7 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
>   	unsigned int total_modes_count = 0;
>   	struct drm_client_offset *offsets;
>   	unsigned int connector_count = 0;
> -	/* points to modes protected by mode_config.mutex */
> -	const struct drm_display_mode **modes;
> +	struct drm_display_mode *modes;
>   	struct drm_crtc **crtcs;
>   	int i, ret = 0;
>   	bool *enabled;
> @@ -858,10 +863,12 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
>   				      crtcs, modes, 0, width, height);
>   	}
>   
> +	mutex_unlock(&dev->mode_config.mutex);
> +
>   	drm_client_modeset_release(client);
>   
>   	for (i = 0; i < connector_count; i++) {
> -		const struct drm_display_mode *mode = modes[i];
> +		const struct drm_display_mode *mode = &modes[i];
>   		struct drm_crtc *crtc = crtcs[i];
>   		struct drm_client_offset *offset = &offsets[i];
>   
> @@ -892,7 +899,6 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
>   			modeset->y = offset->y;
>   		}
>   	}
> -	mutex_unlock(&dev->mode_config.mutex);
>   
>   	mutex_unlock(&client->modeset_mutex);
>   out:
Ville Syrjälä Oct. 8, 2024, 7:33 p.m. UTC | #3
On Mon, Oct 07, 2024 at 09:36:13AM +0200, Thomas Zimmermann wrote:
> Hi
> 
> Am 03.10.24 um 13:33 schrieb Ville Syrjala:
> > From: Ville Syrjälä <ville.syrjala@linux.intel.com>
> >
> > drm_client_firmware_config() is currently picking up the current
> > mode of the crtc via the legacy crtc->mode, which is not supposed
> > to be used by atomic drivers at all. We can't simply switch over
> > to the proper crtc->state->mode because we drop the crtc->mutex
> > (which protects crtc->state) before the mode gets used.
> >
> > The most straightforward solution to extend the lifetime of
> > modes[] seem to be to make full copies of the modes instead
> > of just storing pointers. We do have to replace the NULL checks
> > with something else though. Checking that mode->clock!=0
> > should be sufficient.
> >
> > And with this we can undo also commit 3eadd887dbac
> > ("drm/client:Fully protect modes[] with dev->mode_config.mutex")
> > as the lifetime of modes[] no longer has anything to do with
> > that lock.
> 
> I think it would be a lot better to first build that mode list while 
> holding the mutex, and afterwards copy the resulting modes before 
> releasing the lock. The code below is convoluted with drm_mode_copy().

My first thought was to make copies but still keep track
of pointers. That idea was a complete disaster because you
now had to carefully free the modes on the list.

I then considred some kind of double list approach, but that
too seemed more complicated/confusing than the (IMO fairly
straightforward) apporach I ended up with. I'd prefer to reduce
the nummber of arrays this thing uses rather than increase them.

> 
> >
> > Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
> > ---
> >   drivers/gpu/drm/drm_client_modeset.c | 80 +++++++++++++++-------------
> >   1 file changed, 43 insertions(+), 37 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
> > index 888323137a6a..d413e119db3f 100644
> > --- a/drivers/gpu/drm/drm_client_modeset.c
> > +++ b/drivers/gpu/drm/drm_client_modeset.c
> > @@ -265,10 +265,15 @@ static void drm_client_connectors_enabled(struct drm_connector *connectors[],
> >   		enabled[i] = drm_connector_enabled(connectors[i], false);
> >   }
> >   
> > +static bool mode_valid(const struct drm_display_mode *mode)
> > +{
> > +	return mode->clock != 0;
> 
> A mode's clock isn't always known and not all drivers might set it 
> correctly. At least in simpledrm/ofdrm, we have to make up a clock value 
> for the firmware framebuffer. Otherwise some of our userspace would oops.
> 
> The test for clock != 0 makes sense, but it's maybe the wrong place to 
> do this. Would a test for hdisplay/vdisplay != 0 work instead?

That would work as well. drm_mode_validate_basic() rejects
everything with clock/hdisplay/vdisplay==0.

> 
> > +}
> > +
> >   static bool drm_client_target_cloned(struct drm_device *dev,
> >   				     struct drm_connector *connectors[],
> >   				     unsigned int connector_count,
> > -				     const struct drm_display_mode *modes[],
> > +				     struct drm_display_mode modes[],
> >   				     struct drm_client_offset offsets[],
> >   				     bool enabled[], int width, int height)
> >   {
> > @@ -296,15 +301,16 @@ static bool drm_client_target_cloned(struct drm_device *dev,
> >   	for (i = 0; i < connector_count; i++) {
> >   		if (!enabled[i])
> >   			continue;
> > -		modes[i] = drm_connector_pick_cmdline_mode(connectors[i]);
> > -		if (!modes[i]) {
> > +
> > +		drm_mode_copy(&modes[i], drm_connector_pick_cmdline_mode(connectors[i]));
> > +		if (!mode_valid(&modes[i])) {
> 
> You're copying and only then test for validity?

I thought drm_mode_copy() is a nop for NULL. Turns out I was wrong,
hence the v2.

For this specific case I suppose one could also write something like
 if (whatever_mode())
	drm_mode_copy(&modes[i], whatever_mode());
here, but having to repeat oneself is not so great.

We could of course avoid that with
 mode = whatever_mode();
 if (mode)
	drm_mode_copy(&modes[i], mode);
with the downside of needing yet another local
variable.
kernel test robot Oct. 9, 2024, 2:09 p.m. UTC | #4
hi, Ville Syrjala,

we noticed there is a v2 for this commit
https://lore.kernel.org/all/20241003181553.8891-1-ville.syrjala@linux.intel.com/

but bot failed to analyze the patch mail structure and re-assemble it with
other patches in this serial to form a new branch (which need manual efforts).

so we just made this report out FYI. in case you are sure the issue should be
addressed by v2, please just ignore. if you want us to test v2, please let us
know. thanks


Hello,

kernel test robot noticed "Oops:general_protection_fault,probably_for_non-canonical_address#:#[##]PREEMPT_SMP_KASAN_PTI" on:

commit: 2cc919cccbb5d887534545618d696db4ec5fb691 ("[PATCH 4/8] drm/client: Make copies of modes")
url: https://github.com/intel-lab-lkp/linux/commits/Ville-Syrjala/drm-client-Constify-modes/20241004-061843
base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link: https://lore.kernel.org/all/20241003113304.11700-5-ville.syrjala@linux.intel.com/
patch subject: [PATCH 4/8] drm/client: Make copies of modes

in testcase: boot

compiler: gcc-12
test machine: qemu-system-x86_64 -enable-kvm -cpu SandyBridge -smp 2 -m 16G

(please refer to attached dmesg/kmsg for entire log/backtrace)


+----------------------------------------------------------------------------------------------+------------+------------+
|                                                                                              | 40327b7031 | 2cc919cccb |
+----------------------------------------------------------------------------------------------+------------+------------+
| boot_successes                                                                               | 15         | 0          |
| boot_failures                                                                                | 0          | 16         |
| Oops:general_protection_fault,probably_for_non-canonical_address#:#[##]PREEMPT_SMP_KASAN_PTI | 0          | 16         |
| KASAN:null-ptr-deref_in_range[#-#]                                                           | 0          | 16         |
| RIP:drm_mode_copy[drm]                                                                       | 0          | 16         |
| Kernel_panic-not_syncing:Fatal_exception                                                     | 0          | 16         |
+----------------------------------------------------------------------------------------------+------------+------------+


If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <oliver.sang@intel.com>
| Closes: https://lore.kernel.org/oe-lkp/202410091649.1353a717-oliver.sang@intel.com


[   12.729071][  T116] bochs-drm 0000:00:02.0: vgaarb: deactivate vga console
[   12.733522][  T116] Console: switching to colour dummy device 80x25
[   12.738211][  T116] [drm] Found bochs VGA, ID 0xb0c5.
[   12.738603][  T116] [drm] Framebuffer size 16384 kB @ 0xfd000000, mmio @ 0xfebf0000.
[   12.742172][  T116] [drm] Initialized bochs-drm 1.0.0 for 0000:00:02.0 on minor 0
[   12.744751][  T116] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] PREEMPT SMP KASAN PTI
[   12.745622][  T116] KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
[   12.746193][  T116] CPU: 1 UID: 0 PID: 116 Comm: udevd Not tainted 6.12.0-rc1-00311-g2cc919cccbb5 #2
[   12.746817][  T116] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
[ 12.747510][ T116] RIP: 0010:drm_mode_copy (kbuild/src/consumer/drivers/gpu/drm/drm_modes.c:1422) drm
[ 12.748000][ T116] Code: 40 84 c6 0f 85 01 01 00 00 84 c9 0f 95 c2 0f 9e c0 84 c2 0f 85 f1 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 e8 48 c1 e8 03 <0f> b6 0c 10 48 8d 45 77 48 89 c6 83 e0 07 48 c1 ee 03 0f b6 14 16
All code
========
   0:	40 84 c6             	test   %al,%sil
   3:	0f 85 01 01 00 00    	jne    0x10a
   9:	84 c9                	test   %cl,%cl
   b:	0f 95 c2             	setne  %dl
   e:	0f 9e c0             	setle  %al
  11:	84 c2                	test   %al,%dl
  13:	0f 85 f1 00 00 00    	jne    0x10a
  19:	48 ba 00 00 00 00 00 	movabs $0xdffffc0000000000,%rdx
  20:	fc ff df 
  23:	48 89 e8             	mov    %rbp,%rax
  26:	48 c1 e8 03          	shr    $0x3,%rax
  2a:*	0f b6 0c 10          	movzbl (%rax,%rdx,1),%ecx		<-- trapping instruction
  2e:	48 8d 45 77          	lea    0x77(%rbp),%rax
  32:	48 89 c6             	mov    %rax,%rsi
  35:	83 e0 07             	and    $0x7,%eax
  38:	48 c1 ee 03          	shr    $0x3,%rsi
  3c:	0f b6 14 16          	movzbl (%rsi,%rdx,1),%edx

Code starting with the faulting instruction
===========================================
   0:	0f b6 0c 10          	movzbl (%rax,%rdx,1),%ecx
   4:	48 8d 45 77          	lea    0x77(%rbp),%rax
   8:	48 89 c6             	mov    %rax,%rsi
   b:	83 e0 07             	and    $0x7,%eax
   e:	48 c1 ee 03          	shr    $0x3,%rsi
  12:	0f b6 14 16          	movzbl (%rsi,%rdx,1),%edx
[   12.749333][  T116] RSP: 0000:ffffc900007ff548 EFLAGS: 00010246
[   12.749749][  T116] RAX: 0000000000000000 RBX: ffff8881819c6600 RCX: 0000000000000000
[   12.750284][  T116] RDX: dffffc0000000000 RSI: 1ffff11030338c01 RDI: ffff8881819c6648
[   12.750820][  T116] RBP: 0000000000000000 R08: 0000000000000001 R09: 0000000000000001
[   12.751354][  T116] R10: 0000000000000000 R11: dffffc0000000000 R12: 0000000000000000
[   12.751889][  T116] R13: 0000000000000000 R14: 0000000000000001 R15: ffff8881819c6600
[   12.752435][  T116] FS:  0000000000000000(0000) GS:ffff8883a8f00000(0063) knlGS:00000000f7cec740
[   12.753057][  T116] CS:  0010 DS: 002b ES: 002b CR0: 0000000080050033
[   12.753537][  T116] CR2: 00000000ffc27bf8 CR3: 0000000139596000 CR4: 00000000000406f0
[   12.754112][  T116] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[   12.754674][  T116] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[   12.755239][  T116] Call Trace:
[   12.755479][  T116]  <TASK>
[ 12.755686][ T116] ? die_addr (kbuild/src/consumer/arch/x86/kernel/dumpstack.c:421 kbuild/src/consumer/arch/x86/kernel/dumpstack.c:460) 
[ 12.755977][ T116] ? exc_general_protection (kbuild/src/consumer/arch/x86/kernel/traps.c:751 kbuild/src/consumer/arch/x86/kernel/traps.c:693) 
[ 12.756372][ T116] ? asm_exc_general_protection (kbuild/src/consumer/arch/x86/include/asm/idtentry.h:617) 
[ 12.756757][ T116] ? drm_mode_copy (kbuild/src/consumer/drivers/gpu/drm/drm_modes.c:1422) drm


The kernel config and materials to reproduce are available at:
https://download.01.org/0day-ci/archive/20241009/202410091649.1353a717-oliver.sang@intel.com
Ville Syrjälä Oct. 10, 2024, 7:28 p.m. UTC | #5
On Tue, Oct 08, 2024 at 10:33:44PM +0300, Ville Syrjälä wrote:
> On Mon, Oct 07, 2024 at 09:36:13AM +0200, Thomas Zimmermann wrote:
> > Hi
> > 
> > Am 03.10.24 um 13:33 schrieb Ville Syrjala:
> > > From: Ville Syrjälä <ville.syrjala@linux.intel.com>
> > >
> > > drm_client_firmware_config() is currently picking up the current
> > > mode of the crtc via the legacy crtc->mode, which is not supposed
> > > to be used by atomic drivers at all. We can't simply switch over
> > > to the proper crtc->state->mode because we drop the crtc->mutex
> > > (which protects crtc->state) before the mode gets used.
> > >
> > > The most straightforward solution to extend the lifetime of
> > > modes[] seem to be to make full copies of the modes instead
> > > of just storing pointers. We do have to replace the NULL checks
> > > with something else though. Checking that mode->clock!=0
> > > should be sufficient.
> > >
> > > And with this we can undo also commit 3eadd887dbac
> > > ("drm/client:Fully protect modes[] with dev->mode_config.mutex")
> > > as the lifetime of modes[] no longer has anything to do with
> > > that lock.
> > 
> > I think it would be a lot better to first build that mode list while 
> > holding the mutex, and afterwards copy the resulting modes before 
> > releasing the lock. The code below is convoluted with drm_mode_copy().
> 
> My first thought was to make copies but still keep track
> of pointers. That idea was a complete disaster because you
> now had to carefully free the modes on the list.
> 
> I then considred some kind of double list approach, but that
> too seemed more complicated/confusing than the (IMO fairly
> straightforward) apporach I ended up with. I'd prefer to reduce
> the nummber of arrays this thing uses rather than increase them.

Had another look at the double array approach, and still
tought the result would be quite disgusting.

So I think the only other viable option is to keep the single
array of pointers, and stick copies onto it. But that introduces
more ways to leak memory and/or access already freed memory.
I don't really like the extra complexity that this requires.
It'd perhaps be more palatable if the whole thing would be
redesigned to be more AoS instead of SoA...
diff mbox series

Patch

diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
index 888323137a6a..d413e119db3f 100644
--- a/drivers/gpu/drm/drm_client_modeset.c
+++ b/drivers/gpu/drm/drm_client_modeset.c
@@ -265,10 +265,15 @@  static void drm_client_connectors_enabled(struct drm_connector *connectors[],
 		enabled[i] = drm_connector_enabled(connectors[i], false);
 }
 
+static bool mode_valid(const struct drm_display_mode *mode)
+{
+	return mode->clock != 0;
+}
+
 static bool drm_client_target_cloned(struct drm_device *dev,
 				     struct drm_connector *connectors[],
 				     unsigned int connector_count,
-				     const struct drm_display_mode *modes[],
+				     struct drm_display_mode modes[],
 				     struct drm_client_offset offsets[],
 				     bool enabled[], int width, int height)
 {
@@ -296,15 +301,16 @@  static bool drm_client_target_cloned(struct drm_device *dev,
 	for (i = 0; i < connector_count; i++) {
 		if (!enabled[i])
 			continue;
-		modes[i] = drm_connector_pick_cmdline_mode(connectors[i]);
-		if (!modes[i]) {
+
+		drm_mode_copy(&modes[i], drm_connector_pick_cmdline_mode(connectors[i]));
+		if (!mode_valid(&modes[i])) {
 			can_clone = false;
 			break;
 		}
 		for (j = 0; j < i; j++) {
 			if (!enabled[j])
 				continue;
-			if (!drm_mode_match(modes[j], modes[i],
+			if (!drm_mode_match(&modes[j], &modes[i],
 					    DRM_MODE_MATCH_TIMINGS |
 					    DRM_MODE_MATCH_CLOCK |
 					    DRM_MODE_MATCH_FLAGS |
@@ -335,9 +341,9 @@  static bool drm_client_target_cloned(struct drm_device *dev,
 					   DRM_MODE_MATCH_CLOCK |
 					   DRM_MODE_MATCH_FLAGS |
 					   DRM_MODE_MATCH_3D_FLAGS))
-				modes[i] = mode;
+				drm_mode_copy(&modes[i], mode);
 		}
-		if (!modes[i])
+		if (!mode_valid(&modes[i]))
 			can_clone = false;
 	}
 	drm_mode_destroy(dev, dmt_mode);
@@ -354,7 +360,7 @@  static bool drm_client_target_cloned(struct drm_device *dev,
 static int drm_client_get_tile_offsets(struct drm_device *dev,
 				       struct drm_connector *connectors[],
 				       unsigned int connector_count,
-				       const struct drm_display_mode *modes[],
+				       const struct drm_display_mode modes[],
 				       struct drm_client_offset offsets[],
 				       int idx,
 				       int h_idx, int v_idx)
@@ -368,17 +374,17 @@  static int drm_client_get_tile_offsets(struct drm_device *dev,
 		if (!connector->has_tile)
 			continue;
 
-		if (!modes[i] && (h_idx || v_idx)) {
+		if (!mode_valid(&modes[i]) && (h_idx || v_idx)) {
 			drm_dbg_kms(dev,
 				    "[CONNECTOR:%d:%s] no modes for connector tiled %d\n",
 				    connector->base.id, connector->name, i);
 			continue;
 		}
 		if (connector->tile_h_loc < h_idx)
-			hoffset += modes[i]->hdisplay;
+			hoffset += modes[i].hdisplay;
 
 		if (connector->tile_v_loc < v_idx)
-			voffset += modes[i]->vdisplay;
+			voffset += modes[i].vdisplay;
 	}
 	offsets[idx].x = hoffset;
 	offsets[idx].y = voffset;
@@ -389,7 +395,7 @@  static int drm_client_get_tile_offsets(struct drm_device *dev,
 static bool drm_client_target_preferred(struct drm_device *dev,
 					struct drm_connector *connectors[],
 					unsigned int connector_count,
-					const struct drm_display_mode *modes[],
+					struct drm_display_mode modes[],
 					struct drm_client_offset offsets[],
 					bool enabled[], int width, int height)
 {
@@ -445,16 +451,16 @@  static bool drm_client_target_preferred(struct drm_device *dev,
 		}
 
 		mode_type = "cmdline";
-		modes[i] = drm_connector_pick_cmdline_mode(connector);
+		drm_mode_copy(&modes[i], drm_connector_pick_cmdline_mode(connector));
 
-		if (!modes[i]) {
+		if (!mode_valid(&modes[i])) {
 			mode_type = "preferred";
-			modes[i] = drm_connector_preferred_mode(connector, width, height);
+			drm_mode_copy(&modes[i], drm_connector_preferred_mode(connector, width, height));
 		}
 
-		if (!modes[i]) {
+		if (!mode_valid(&modes[i])) {
 			mode_type = "first";
-			modes[i] = drm_connector_first_mode(connector);
+			drm_mode_copy(&modes[i], drm_connector_first_mode(connector));
 		}
 
 		/*
@@ -472,17 +478,17 @@  static bool drm_client_target_preferred(struct drm_device *dev,
 			     connector->tile_v_loc == 0 &&
 			     !drm_connector_get_tiled_mode(connector))) {
 				mode_type = "non tiled";
-				modes[i] = drm_connector_fallback_non_tiled_mode(connector);
+				drm_mode_copy(&modes[i], drm_connector_fallback_non_tiled_mode(connector));
 			} else {
 				mode_type = "tiled";
-				modes[i] = drm_connector_get_tiled_mode(connector);
+				drm_mode_copy(&modes[i], drm_connector_get_tiled_mode(connector));
 			}
 		}
 
-		if (modes[i])
+		if (mode_valid(&modes[i]))
 			drm_dbg_kms(dev, "[CONNECTOR:%d:%s] found %s mode: %s\n",
 				    connector->base.id, connector->name,
-				    mode_type, modes[i]->name);
+				    mode_type, modes[i].name);
 		else
 			drm_dbg_kms(dev, "[CONNECTOR:%d:%s] no mode found\n",
 				    connector->base.id, connector->name);
@@ -514,7 +520,7 @@  static int drm_client_pick_crtcs(struct drm_client_dev *client,
 				 struct drm_connector *connectors[],
 				 unsigned int connector_count,
 				 struct drm_crtc *best_crtcs[],
-				 const struct drm_display_mode *modes[],
+				 const struct drm_display_mode modes[],
 				 int n, int width, int height)
 {
 	struct drm_device *dev = client->dev;
@@ -532,7 +538,7 @@  static int drm_client_pick_crtcs(struct drm_client_dev *client,
 	best_crtcs[n] = NULL;
 	best_score = drm_client_pick_crtcs(client, connectors, connector_count,
 					   best_crtcs, modes, n + 1, width, height);
-	if (modes[n] == NULL)
+	if (!mode_valid(&modes[n]))
 		return best_score;
 
 	crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL);
@@ -566,7 +572,7 @@  static int drm_client_pick_crtcs(struct drm_client_dev *client,
 			if (dev->mode_config.num_crtc > 1)
 				continue;
 
-			if (!drm_mode_equal(modes[o], modes[n]))
+			if (!drm_mode_equal(&modes[o], &modes[n]))
 				continue;
 		}
 
@@ -589,7 +595,7 @@  static bool drm_client_firmware_config(struct drm_client_dev *client,
 				       struct drm_connector *connectors[],
 				       unsigned int connector_count,
 				       struct drm_crtc *crtcs[],
-				       const struct drm_display_mode *modes[],
+				       struct drm_display_mode modes[],
 				       struct drm_client_offset offsets[],
 				       bool enabled[], int width, int height)
 {
@@ -690,20 +696,20 @@  static bool drm_client_firmware_config(struct drm_client_dev *client,
 		}
 
 		mode_type = "cmdline";
-		modes[i] = drm_connector_pick_cmdline_mode(connector);
+		drm_mode_copy(&modes[i], drm_connector_pick_cmdline_mode(connector));
 
-		if (!modes[i]) {
+		if (!mode_valid(&modes[i])) {
 			mode_type = "preferred";
-			modes[i] = drm_connector_preferred_mode(connector, width, height);
+			drm_mode_copy(&modes[i], drm_connector_preferred_mode(connector, width, height));
 		}
 
-		if (!modes[i]) {
+		if (!mode_valid(&modes[i])) {
 			mode_type = "first";
-			modes[i] = drm_connector_first_mode(connector);
+			drm_mode_copy(&modes[i], drm_connector_first_mode(connector));
 		}
 
 		/* last resort: use current mode */
-		if (!modes[i]) {
+		if (!mode_valid(&modes[i])) {
 			/*
 			 * IMPORTANT: We want to use the adjusted mode (i.e.
 			 * after the panel fitter upscaling) as the initial
@@ -716,7 +722,7 @@  static bool drm_client_firmware_config(struct drm_client_dev *client,
 			 * fastboot check to work correctly.
 			 */
 			mode_type = "current";
-			modes[i] = &connector->state->crtc->mode;
+			drm_mode_copy(&modes[i], &connector->state->crtc->mode);
 		}
 
 		/*
@@ -726,14 +732,14 @@  static bool drm_client_firmware_config(struct drm_client_dev *client,
 		if (connector->has_tile &&
 		    num_tiled_conns < connector->num_h_tile * connector->num_v_tile) {
 			mode_type = "non tiled";
-			modes[i] = drm_connector_fallback_non_tiled_mode(connector);
+			drm_mode_copy(&modes[i], drm_connector_fallback_non_tiled_mode(connector));
 		}
 		crtcs[i] = new_crtc;
 
 		drm_dbg_kms(dev, "[CONNECTOR::%d:%s] on [CRTC:%d:%s] using %s mode: %s\n",
 			    connector->base.id, connector->name,
 			    new_crtc->base.id, new_crtc->name,
-			    mode_type, modes[i]->name);
+			    mode_type, modes[i].name);
 
 		fallback = false;
 		conn_configured |= BIT(i);
@@ -789,8 +795,7 @@  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
 	unsigned int total_modes_count = 0;
 	struct drm_client_offset *offsets;
 	unsigned int connector_count = 0;
-	/* points to modes protected by mode_config.mutex */
-	const struct drm_display_mode **modes;
+	struct drm_display_mode *modes;
 	struct drm_crtc **crtcs;
 	int i, ret = 0;
 	bool *enabled;
@@ -858,10 +863,12 @@  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
 				      crtcs, modes, 0, width, height);
 	}
 
+	mutex_unlock(&dev->mode_config.mutex);
+
 	drm_client_modeset_release(client);
 
 	for (i = 0; i < connector_count; i++) {
-		const struct drm_display_mode *mode = modes[i];
+		const struct drm_display_mode *mode = &modes[i];
 		struct drm_crtc *crtc = crtcs[i];
 		struct drm_client_offset *offset = &offsets[i];
 
@@ -892,7 +899,6 @@  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
 			modeset->y = offset->y;
 		}
 	}
-	mutex_unlock(&dev->mode_config.mutex);
 
 	mutex_unlock(&client->modeset_mutex);
 out: