[PATCH v3 6/7] drm/vc4: kms: Store the unassigned channel list in the state

Thomas Zimmermann tzimmermann at suse.de
Thu Nov 19 08:59:15 UTC 2020


Hi

Am 05.11.20 um 14:56 schrieb Maxime Ripard:
> If a CRTC is enabled but not active, and that we're then doing a page
> flip on another CRTC, drm_atomic_get_crtc_state will bring the first
> CRTC state into the global state, and will make us wait for its vblank
> as well, even though that might never occur.
> 
> Instead of creating the list of the free channels each time atomic_check
> is called, and calling drm_atomic_get_crtc_state to retrieve the
> allocated channels, let's create a private state object in the main
> atomic state, and use it to store the available channels.
> 
> Since vc4 has a semaphore (with a value of 1, so a lock) in its commit
> implementation to serialize all the commits, even the nonblocking ones, we
> are free from the use-after-free race if two subsequent commits are not ran
> in their submission order.
> 
> Fixes: 87ebcd42fb7b ("drm/vc4: crtc: Assign output to channel automatically")
> Reviewed-by: Hoegeun Kwon <hoegeun.kwon at samsung.com>
> Tested-by: Hoegeun Kwon <hoegeun.kwon at samsung.com>
> Signed-off-by: Maxime Ripard <maxime at cerno.tech>
> ---
>   drivers/gpu/drm/vc4/vc4_drv.h |   1 +
>   drivers/gpu/drm/vc4/vc4_kms.c | 124 +++++++++++++++++++++++++++-------
>   2 files changed, 100 insertions(+), 25 deletions(-)
> 
> diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h
> index bdbb9540d47d..014113823647 100644
> --- a/drivers/gpu/drm/vc4/vc4_drv.h
> +++ b/drivers/gpu/drm/vc4/vc4_drv.h
> @@ -219,6 +219,7 @@ struct vc4_dev {
>   
>   	struct drm_modeset_lock ctm_state_lock;
>   	struct drm_private_obj ctm_manager;
> +	struct drm_private_obj hvs_channels;
>   	struct drm_private_obj load_tracker;
>   
>   	/* List of vc4_debugfs_info_entry for adding to debugfs once
> diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c
> index 499c6914fce4..0a231ae500e5 100644
> --- a/drivers/gpu/drm/vc4/vc4_kms.c
> +++ b/drivers/gpu/drm/vc4/vc4_kms.c
> @@ -37,6 +37,17 @@ static struct vc4_ctm_state *to_vc4_ctm_state(struct drm_private_state *priv)
>   	return container_of(priv, struct vc4_ctm_state, base);
>   }
>   
> +struct vc4_hvs_state {
> +	struct drm_private_state base;
> +	unsigned int unassigned_channels;
> +};
> +
> +static struct vc4_hvs_state *
> +to_vc4_hvs_state(struct drm_private_state *priv)
> +{
> +	return container_of(priv, struct vc4_hvs_state, base);
> +}
> +
>   struct vc4_load_tracker_state {
>   	struct drm_private_state base;
>   	u64 hvs_load;
> @@ -662,6 +673,70 @@ static int vc4_load_tracker_obj_init(struct vc4_dev *vc4)
>   	return drmm_add_action_or_reset(&vc4->base, vc4_load_tracker_obj_fini, NULL);
>   }
>   
> +static struct drm_private_state *
> +vc4_hvs_channels_duplicate_state(struct drm_private_obj *obj)
> +{
> +	struct vc4_hvs_state *state;
> +
> +	state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL);
> +	if (!state)
> +		return NULL;
> +
> +	__drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
> +
> +	return &state->base;
> +}
> +
> +static void vc4_hvs_channels_destroy_state(struct drm_private_obj *obj,
> +					   struct drm_private_state *state)
> +{
> +	struct vc4_hvs_state *hvs_state;
> +
> +	hvs_state = to_vc4_hvs_state(state);
> +	kfree(hvs_state);
> +}
> +
> +static const struct drm_private_state_funcs vc4_hvs_state_funcs = {
> +	.atomic_duplicate_state = vc4_hvs_channels_duplicate_state,
> +	.atomic_destroy_state = vc4_hvs_channels_destroy_state,
> +};
> +
> +static void vc4_hvs_channels_obj_fini(struct drm_device *dev, void *unused)
> +{
> +	struct vc4_dev *vc4 = to_vc4_dev(dev);
> +
> +	drm_atomic_private_obj_fini(&vc4->hvs_channels);
> +}
> +
> +static int vc4_hvs_channels_obj_init(struct vc4_dev *vc4)
> +{
> +	struct vc4_hvs_state *state;
> +
> +	state = kzalloc(sizeof(*state), GFP_KERNEL);
> +	if (!state)
> +		return -ENOMEM;
> +
> +	state->unassigned_channels = GENMASK(HVS_NUM_CHANNELS - 1, 0);
> +	drm_atomic_private_obj_init(&vc4->base, &vc4->hvs_channels,
> +				    &state->base,
> +				    &vc4_hvs_state_funcs);
> +
> +	return drmm_add_action_or_reset(&vc4->base, vc4_hvs_channels_obj_fini, NULL);
> +}
> +
> +static struct vc4_hvs_state *
> +vc4_hvs_get_global_state(struct drm_atomic_state *state)
> +{
> +	struct vc4_dev *vc4 = to_vc4_dev(state->dev);
> +	struct drm_private_state *priv_state;
> +
> +	priv_state = drm_atomic_get_private_obj_state(state, &vc4->hvs_channels);
> +	if (IS_ERR(priv_state))
> +		return ERR_CAST(priv_state);
> +
> +	return to_vc4_hvs_state(priv_state);
> +}
> +
>   /*
>    * The BCM2711 HVS has up to 7 output connected to the pixelvalves and
>    * the TXP (and therefore all the CRTCs found on that platform).
> @@ -678,6 +753,14 @@ static int vc4_load_tracker_obj_init(struct vc4_dev *vc4)
>    *   need to consider all the running CRTCs in the DRM device to assign
>    *   a FIFO, not just the one in the state.
>    *
> + * - To fix the above, we can't use drm_atomic_get_crtc_state on all
> + *   enabled CRTCs to pull their CRTC state into the global state, since
> + *   a page flip would start considering their vblank to complete. Since
> + *   we don't have a guarantee that they are actually active, that
> + *   vblank might never happen, and shouldn't even be considered if we
> + *   want to do a page flip on a single CRTC. That can be tested by
> + *   doing a modetest -v first on HDMI1 and then on HDMI0.
> + *
>    * - Since we need the pixelvalve to be disabled and enabled back when
>    *   the FIFO is changed, we should keep the FIFO assigned for as long
>    *   as the CRTC is enabled, only considering it free again once that
> @@ -687,46 +770,33 @@ static int vc4_load_tracker_obj_init(struct vc4_dev *vc4)
>   static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
>   				      struct drm_atomic_state *state)
>   {
> -	unsigned long unassigned_channels = GENMASK(HVS_NUM_CHANNELS - 1, 0);
> +	struct vc4_hvs_state *hvs_state;
>   	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
>   	struct drm_crtc *crtc;
>   	unsigned int i;
>   
> -	/*
> -	 * Since the HVS FIFOs are shared across all the pixelvalves and
> -	 * the TXP (and thus all the CRTCs), we need to pull the current
> -	 * state of all the enabled CRTCs so that an update to a single
> -	 * CRTC still keeps the previous FIFOs enabled and assigned to
> -	 * the same CRTCs, instead of evaluating only the CRTC being
> -	 * modified.
> -	 */
> -	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
> -		struct drm_crtc_state *crtc_state;
> -
> -		if (!crtc->state->enable)
> -			continue;
> -
> -		crtc_state = drm_atomic_get_crtc_state(state, crtc);
> -		if (IS_ERR(crtc_state))
> -			return PTR_ERR(crtc_state);
> -	}
> +	hvs_state = vc4_hvs_get_global_state(state);
> +	if (!hvs_state)
> +		return -EINVAL;

I found this confusing. It's technically correct, but from hvs_state is 
not clear that it's the new state. Maybe call it hvs_new_state.

If you want to be pedantic, maybe split the creation of the new state 
from the usage. Call vc4_hvs_get_global_state() at the top of 
vc4_atomic_check() to make the new state. (Maybe with a short comment.) 
And here only call an equivalent of 
drm_atomic_get_new_private_obj_state() for hvs_channels.

In any case

Reviewed-by: Thomas Zimmermann <tzimmermann at suse.de>

Best regards
Thomas

>   
>   	for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
> +		struct vc4_crtc_state *old_vc4_crtc_state =
> +			to_vc4_crtc_state(old_crtc_state);
>   		struct vc4_crtc_state *new_vc4_crtc_state =
>   			to_vc4_crtc_state(new_crtc_state);
>   		struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
>   		unsigned int matching_channels;
>   
> -		if (old_crtc_state->enable && !new_crtc_state->enable)
> +		if (old_crtc_state->enable && !new_crtc_state->enable) {
> +			hvs_state->unassigned_channels |= BIT(old_vc4_crtc_state->assigned_channel);
>   			new_vc4_crtc_state->assigned_channel = VC4_HVS_CHANNEL_DISABLED;
> +		}
>   
>   		if (!new_crtc_state->enable)
>   			continue;
>   
> -		if (new_vc4_crtc_state->assigned_channel != VC4_HVS_CHANNEL_DISABLED) {
> -			unassigned_channels &= ~BIT(new_vc4_crtc_state->assigned_channel);
> +		if (new_vc4_crtc_state->assigned_channel != VC4_HVS_CHANNEL_DISABLED)
>   			continue;
> -		}
>   
>   		/*
>   		 * The problem we have to solve here is that we have
> @@ -752,12 +822,12 @@ static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
>   		 * the future, we will need to have something smarter,
>   		 * but it works so far.
>   		 */
> -		matching_channels = unassigned_channels & vc4_crtc->data->hvs_available_channels;
> +		matching_channels = hvs_state->unassigned_channels & vc4_crtc->data->hvs_available_channels;
>   		if (matching_channels) {
>   			unsigned int channel = ffs(matching_channels) - 1;
>   
>   			new_vc4_crtc_state->assigned_channel = channel;
> -			unassigned_channels &= ~BIT(channel);
> +			hvs_state->unassigned_channels &= ~BIT(channel);
>   		} else {
>   			return -EINVAL;
>   		}
> @@ -841,6 +911,10 @@ int vc4_kms_load(struct drm_device *dev)
>   	if (ret)
>   		return ret;
>   
> +	ret = vc4_hvs_channels_obj_init(vc4);
> +	if (ret)
> +		return ret;
> +
>   	drm_mode_config_reset(dev);
>   
>   	drm_kms_helper_poll_init(dev);
> 

-- 
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Maxfeldstr. 5, 90409 Nürnberg, Germany
(HRB 36809, AG Nürnberg)
Geschäftsführer: Felix Imendörffer
-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_0x680DC11D530B7A23.asc
Type: application/pgp-keys
Size: 7435 bytes
Desc: not available
URL: <https://lists.freedesktop.org/archives/dri-devel/attachments/20201119/a04970d2/attachment-0001.key>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_signature
Type: application/pgp-signature
Size: 840 bytes
Desc: OpenPGP digital signature
URL: <https://lists.freedesktop.org/archives/dri-devel/attachments/20201119/a04970d2/attachment-0001.sig>


More information about the dri-devel mailing list