[v2,02/13] drm/i915: Rework global state locking
diff mbox series

Message ID 20191015193035.25982-3-ville.syrjala@linux.intel.com
State New
Headers show
Series
  • drm/i915: Plane cdclk requirements and fp16 for gen4+
Related show

Commit Message

Ville Syrjälä Oct. 15, 2019, 7:30 p.m. UTC
From: Ville Syrjälä <ville.syrjala@linux.intel.com>

So far we've sort of protected the global state under dev_priv with
the connection_mutex. I wan to change that so that we can change the
cdclk even for pure plane updates. To that end let's formalize the
protection of the global state to follow what I started with the cdclk
code already (though not entirely properly) such that any crtc mutex
will suffice as a read lock, and all crtcs mutexes act as the write
lock.

We'll also pimp intel_atomic_state_clear() to clear the entire global
state, so that we don't accidentally leak stale information between
the locking retries.

As a slight optimization we'll only lock the crtc mutexes to protect
the global state, however if and when we actually have to poke the
hw (eg. if the actual cdclk changes) we must serialize commits
across all crtcs so that a parallel nonblocking commit can't get
ahead of the cdclk reprogamming. We do that by adding all crtcs to
the state.

TODO: the old global state examined during commit may still
be a problem since it always looks at the _latest_ swapped state
in dev_priv. Need to add proper old/new state for that too I think.

v2: Remeber to serialize the commits if necessary

Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
---
 drivers/gpu/drm/i915/display/intel_atomic.c   |  44 ++++++++
 drivers/gpu/drm/i915/display/intel_atomic.h   |   5 +
 drivers/gpu/drm/i915/display/intel_audio.c    |  10 +-
 drivers/gpu/drm/i915/display/intel_cdclk.c    | 102 ++++++++++--------
 drivers/gpu/drm/i915/display/intel_display.c  |  29 ++++-
 .../drm/i915/display/intel_display_types.h    |   8 ++
 drivers/gpu/drm/i915/i915_drv.h               |  11 +-
 7 files changed, 153 insertions(+), 56 deletions(-)

Comments

Stanislav Lisovskiy Oct. 24, 2019, 10:24 a.m. UTC | #1
On Tue, 2019-10-15 at 22:30 +0300, Ville Syrjala wrote:
> From: Ville Syrjälä <ville.syrjala@linux.intel.com>
> 
> So far we've sort of protected the global state under dev_priv with
> the connection_mutex. I wan to change that so that we can change the
> cdclk even for pure plane updates. To that end let's formalize the
> protection of the global state to follow what I started with the
> cdclk
> code already (though not entirely properly) such that any crtc mutex
> will suffice as a read lock, and all crtcs mutexes act as the write
> lock.
> 
> We'll also pimp intel_atomic_state_clear() to clear the entire global
> state, so that we don't accidentally leak stale information between
> the locking retries.
> 
> As a slight optimization we'll only lock the crtc mutexes to protect
> the global state, however if and when we actually have to poke the
> hw (eg. if the actual cdclk changes) we must serialize commits
> across all crtcs so that a parallel nonblocking commit can't get
> ahead of the cdclk reprogamming. We do that by adding all crtcs to
> the state.
> 
> TODO: the old global state examined during commit may still
> be a problem since it always looks at the _latest_ swapped state
> in dev_priv. Need to add proper old/new state for that too I think.
> 
> v2: Remeber to serialize the commits if necessary
> 
> Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>

Reviewed-by: Stanislav Lisovskiy <stanislav.lisovskiy@intel.com>

> ---
>  drivers/gpu/drm/i915/display/intel_atomic.c   |  44 ++++++++
>  drivers/gpu/drm/i915/display/intel_atomic.h   |   5 +
>  drivers/gpu/drm/i915/display/intel_audio.c    |  10 +-
>  drivers/gpu/drm/i915/display/intel_cdclk.c    | 102 ++++++++++----
> ----
>  drivers/gpu/drm/i915/display/intel_display.c  |  29 ++++-
>  .../drm/i915/display/intel_display_types.h    |   8 ++
>  drivers/gpu/drm/i915/i915_drv.h               |  11 +-
>  7 files changed, 153 insertions(+), 56 deletions(-)
> 
> diff --git a/drivers/gpu/drm/i915/display/intel_atomic.c
> b/drivers/gpu/drm/i915/display/intel_atomic.c
> index c5a552a69752..9cd6d2348a1e 100644
> --- a/drivers/gpu/drm/i915/display/intel_atomic.c
> +++ b/drivers/gpu/drm/i915/display/intel_atomic.c
> @@ -429,6 +429,13 @@ void intel_atomic_state_clear(struct
> drm_atomic_state *s)
>  	struct intel_atomic_state *state = to_intel_atomic_state(s);
>  	drm_atomic_state_default_clear(&state->base);
>  	state->dpll_set = state->modeset = false;
> +	state->global_state_changed = false;
> +	state->active_pipes = 0;
> +	memset(&state->min_cdclk, 0, sizeof(state->min_cdclk));
> +	memset(&state->min_voltage_level, 0, sizeof(state-
> >min_voltage_level));
> +	memset(&state->cdclk.logical, 0, sizeof(state->cdclk.logical));
> +	memset(&state->cdclk.actual, 0, sizeof(state->cdclk.actual));
> +	state->cdclk.pipe = INVALID_PIPE;
>  }
>  
>  struct intel_crtc_state *
> @@ -442,3 +449,40 @@ intel_atomic_get_crtc_state(struct
> drm_atomic_state *state,
>  
>  	return to_intel_crtc_state(crtc_state);
>  }
> +
> +int intel_atomic_lock_global_state(struct intel_atomic_state *state)
> +{
> +	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
> +	struct intel_crtc *crtc;
> +
> +	state->global_state_changed = true;
> +
> +	for_each_intel_crtc(&dev_priv->drm, crtc) {
> +		int ret;
> +
> +		ret = drm_modeset_lock(&crtc->base.mutex,
> +				       state->base.acquire_ctx);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +int intel_atomic_serialize_global_state(struct intel_atomic_state
> *state)
> +{
> +	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
> +	struct intel_crtc *crtc;
> +
> +	state->global_state_changed = true;
> +
> +	for_each_intel_crtc(&dev_priv->drm, crtc) {
> +		struct intel_crtc_state *crtc_state;
> +
> +		crtc_state = intel_atomic_get_crtc_state(&state->base,
> crtc);
> +		if (IS_ERR(crtc_state))
> +			return PTR_ERR(crtc_state);
> +	}
> +
> +	return 0;
> +}
> diff --git a/drivers/gpu/drm/i915/display/intel_atomic.h
> b/drivers/gpu/drm/i915/display/intel_atomic.h
> index 58065d3161a3..49d5cb1b9e0a 100644
> --- a/drivers/gpu/drm/i915/display/intel_atomic.h
> +++ b/drivers/gpu/drm/i915/display/intel_atomic.h
> @@ -16,6 +16,7 @@ struct drm_crtc_state;
>  struct drm_device;
>  struct drm_i915_private;
>  struct drm_property;
> +struct intel_atomic_state;
>  struct intel_crtc;
>  struct intel_crtc_state;
>  
> @@ -46,4 +47,8 @@ int intel_atomic_setup_scalers(struct
> drm_i915_private *dev_priv,
>  			       struct intel_crtc *intel_crtc,
>  			       struct intel_crtc_state *crtc_state);
>  
> +int intel_atomic_lock_global_state(struct intel_atomic_state
> *state);
> +
> +int intel_atomic_serialize_global_state(struct intel_atomic_state
> *state);
> +
>  #endif /* __INTEL_ATOMIC_H__ */
> diff --git a/drivers/gpu/drm/i915/display/intel_audio.c
> b/drivers/gpu/drm/i915/display/intel_audio.c
> index ed18511befa3..85e6b2bbb34f 100644
> --- a/drivers/gpu/drm/i915/display/intel_audio.c
> +++ b/drivers/gpu/drm/i915/display/intel_audio.c
> @@ -28,6 +28,7 @@
>  #include <drm/i915_component.h>
>  
>  #include "i915_drv.h"
> +#include "intel_atomic.h"
>  #include "intel_audio.h"
>  #include "intel_display_types.h"
>  #include "intel_lpe_audio.h"
> @@ -818,13 +819,8 @@ static void glk_force_audio_cdclk(struct
> drm_i915_private *dev_priv,
>  	to_intel_atomic_state(state)->cdclk.force_min_cdclk =
>  		enable ? 2 * 96000 : 0;
>  
> -	/*
> -	 * Protects dev_priv->cdclk.force_min_cdclk
> -	 * Need to lock this here in case we have no active pipes
> -	 * and thus wouldn't lock it during the commit otherwise.
> -	 */
> -	ret = drm_modeset_lock(&dev_priv-
> >drm.mode_config.connection_mutex,
> -			       &ctx);
> +	/* Protects dev_priv->cdclk.force_min_cdclk */
> +	ret =
> intel_atomic_lock_global_state(to_intel_atomic_state(state));
>  	if (!ret)
>  		ret = drm_atomic_commit(state);
>  
> diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c
> b/drivers/gpu/drm/i915/display/intel_cdclk.c
> index 6eaebc38ee01..6c17a3bbf866 100644
> --- a/drivers/gpu/drm/i915/display/intel_cdclk.c
> +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c
> @@ -2007,11 +2007,20 @@ static int intel_compute_min_cdclk(struct
> intel_atomic_state *state)
>  	       sizeof(state->min_cdclk));
>  
>  	for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) {
> +		int ret;
> +
>  		min_cdclk = intel_crtc_compute_min_cdclk(crtc_state);
>  		if (min_cdclk < 0)
>  			return min_cdclk;
>  
> +		if (state->min_cdclk[i] == min_cdclk)
> +			continue;
> +
>  		state->min_cdclk[i] = min_cdclk;
> +
> +		ret = intel_atomic_lock_global_state(state);
> +		if (ret)
> +			return ret;
>  	}
>  
>  	min_cdclk = state->cdclk.force_min_cdclk;
> @@ -2034,7 +2043,7 @@ static int intel_compute_min_cdclk(struct
> intel_atomic_state *state)
>   * future platforms this code will need to be
>   * adjusted.
>   */
> -static u8 bxt_compute_min_voltage_level(struct intel_atomic_state
> *state)
> +static int bxt_compute_min_voltage_level(struct intel_atomic_state
> *state)
>  {
>  	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
>  	struct intel_crtc *crtc;
> @@ -2047,11 +2056,21 @@ static u8
> bxt_compute_min_voltage_level(struct intel_atomic_state *state)
>  	       sizeof(state->min_voltage_level));
>  
>  	for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) {
> +		int ret;
> +
>  		if (crtc_state->base.enable)
> -			state->min_voltage_level[i] =
> -				crtc_state->min_voltage_level;
> +			min_voltage_level = crtc_state-
> >min_voltage_level;
>  		else
> -			state->min_voltage_level[i] = 0;
> +			min_voltage_level = 0;
> +
> +		if (state->min_voltage_level[i] == min_voltage_level)
> +			continue;
> +
> +		state->min_voltage_level[i] = min_voltage_level;
> +
> +		ret = intel_atomic_lock_global_state(state);
> +		if (ret)
> +			return ret;
>  	}
>  
>  	min_voltage_level = 0;
> @@ -2195,20 +2214,24 @@ static int skl_modeset_calc_cdclk(struct
> intel_atomic_state *state)
>  static int bxt_modeset_calc_cdclk(struct intel_atomic_state *state)
>  {
>  	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
> -	int min_cdclk, cdclk, vco;
> +	int min_cdclk, min_voltage_level, cdclk, vco;
>  
>  	min_cdclk = intel_compute_min_cdclk(state);
>  	if (min_cdclk < 0)
>  		return min_cdclk;
>  
> +	min_voltage_level = bxt_compute_min_voltage_level(state);
> +	if (min_voltage_level < 0)
> +		return min_voltage_level;
> +
>  	cdclk = bxt_calc_cdclk(dev_priv, min_cdclk);
>  	vco = bxt_calc_cdclk_pll_vco(dev_priv, cdclk);
>  
>  	state->cdclk.logical.vco = vco;
>  	state->cdclk.logical.cdclk = cdclk;
>  	state->cdclk.logical.voltage_level =
> -		max(dev_priv->display.calc_voltage_level(cdclk),
> -		    bxt_compute_min_voltage_level(state));
> +		max_t(int, min_voltage_level,
> +		      dev_priv->display.calc_voltage_level(cdclk));
>  
>  	if (!state->active_pipes) {
>  		cdclk = bxt_calc_cdclk(dev_priv, state-
> >cdclk.force_min_cdclk);
> @@ -2225,23 +2248,6 @@ static int bxt_modeset_calc_cdclk(struct
> intel_atomic_state *state)
>  	return 0;
>  }
>  
> -static int intel_lock_all_pipes(struct intel_atomic_state *state)
> -{
> -	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
> -	struct intel_crtc *crtc;
> -
> -	/* Add all pipes to the state */
> -	for_each_intel_crtc(&dev_priv->drm, crtc) {
> -		struct intel_crtc_state *crtc_state;
> -
> -		crtc_state = intel_atomic_get_crtc_state(&state->base,
> crtc);
> -		if (IS_ERR(crtc_state))
> -			return PTR_ERR(crtc_state);
> -	}
> -
> -	return 0;
> -}
> -
>  static int intel_modeset_all_pipes(struct intel_atomic_state *state)
>  {
>  	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
> @@ -2308,46 +2314,56 @@ int intel_modeset_calc_cdclk(struct
> intel_atomic_state *state)
>  		return ret;
>  
>  	/*
> -	 * Writes to dev_priv->cdclk.logical must protected by
> -	 * holding all the crtc locks, even if we don't end up
> +	 * Writes to dev_priv->cdclk.{actual,logical} must protected
> +	 * by holding all the crtc mutexes even if we don't end up
>  	 * touching the hardware
>  	 */
> -	if (intel_cdclk_changed(&dev_priv->cdclk.logical,
> -				&state->cdclk.logical)) {
> -		ret = intel_lock_all_pipes(state);
> -		if (ret < 0)
> +	if (intel_cdclk_changed(&dev_priv->cdclk.actual,
> +				&state->cdclk.actual)) {
> +		/*
> +		 * Also serialize commits across all crtcs
> +		 * if the actual hw needs to be poked.
> +		 */
> +		ret = intel_atomic_serialize_global_state(state);
> +		if (ret)
> +			return ret;
> +	} else if (intel_cdclk_changed(&dev_priv->cdclk.logical,
> +				       &state->cdclk.logical)) {
> +		ret = intel_atomic_lock_global_state(state);
> +		if (ret)
>  			return ret;
> +	} else {
> +		return 0;
>  	}
>  
> -	if (is_power_of_2(state->active_pipes)) {
> +	if (is_power_of_2(state->active_pipes) &&
> +	    intel_cdclk_needs_cd2x_update(dev_priv,
> +					  &dev_priv->cdclk.actual,
> +					  &state->cdclk.actual)) {
>  		struct intel_crtc *crtc;
>  		struct intel_crtc_state *crtc_state;
>  
>  		pipe = ilog2(state->active_pipes);
>  		crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
> -		crtc_state = intel_atomic_get_new_crtc_state(state,
> crtc);
> -		if (crtc_state &&
> -		    drm_atomic_crtc_needs_modeset(&crtc_state->base))
> +
> +		crtc_state = intel_atomic_get_crtc_state(&state->base,
> crtc);
> +		if (IS_ERR(crtc_state))
> +			return PTR_ERR(crtc_state);
> +
> +		if (drm_atomic_crtc_needs_modeset(&crtc_state->base))
>  			pipe = INVALID_PIPE;
>  	} else {
>  		pipe = INVALID_PIPE;
>  	}
>  
> -	/* All pipes must be switched off while we change the cdclk. */
> -	if (pipe != INVALID_PIPE &&
> -	    intel_cdclk_needs_cd2x_update(dev_priv,
> -					  &dev_priv->cdclk.actual,
> -					  &state->cdclk.actual)) {
> -		ret = intel_lock_all_pipes(state);
> -		if (ret)
> -			return ret;
> -
> +	if (pipe != INVALID_PIPE) {
>  		state->cdclk.pipe = pipe;
>  
>  		DRM_DEBUG_KMS("Can change cdclk with pipe %c active\n",
>  			      pipe_name(pipe));
>  	} else if (intel_cdclk_needs_modeset(&dev_priv->cdclk.actual,
>  					     &state->cdclk.actual)) {
> +		/* All pipes must be switched off while we change the
> cdclk. */
>  		ret = intel_modeset_all_pipes(state);
>  		if (ret)
>  			return ret;
> diff --git a/drivers/gpu/drm/i915/display/intel_display.c
> b/drivers/gpu/drm/i915/display/intel_display.c
> index 7d7d1859775a..a8444d9841c1 100644
> --- a/drivers/gpu/drm/i915/display/intel_display.c
> +++ b/drivers/gpu/drm/i915/display/intel_display.c
> @@ -12191,6 +12191,12 @@ static bool
> check_digital_port_conflicts(struct intel_atomic_state *state)
>  	unsigned int used_mst_ports = 0;
>  	bool ret = true;
>  
> +	/*
> +	 * We're going to peek into connector->state,
> +	 * hence connection_mutex must be held.
> +	 */
> +	drm_modeset_lock_assert_held(&dev-
> >mode_config.connection_mutex);
> +
>  	/*
>  	 * Walk the connector list instead of the encoder
>  	 * list to detect the problem on ddi platforms
> @@ -13463,7 +13469,6 @@ static int intel_modeset_checks(struct
> intel_atomic_state *state)
>  	state->active_pipes = dev_priv->active_pipes;
>  	state->cdclk.logical = dev_priv->cdclk.logical;
>  	state->cdclk.actual = dev_priv->cdclk.actual;
> -	state->cdclk.pipe = INVALID_PIPE;
>  
>  	for_each_oldnew_intel_crtc_in_state(state, crtc,
> old_crtc_state,
>  					    new_crtc_state, i) {
> @@ -13476,6 +13481,12 @@ static int intel_modeset_checks(struct
> intel_atomic_state *state)
>  			state->active_pipe_changes |= BIT(crtc->pipe);
>  	}
>  
> +	if (state->active_pipe_changes) {
> +		ret = intel_atomic_lock_global_state(state);
> +		if (ret)
> +			return ret;
> +	}
> +
>  	ret = intel_modeset_calc_cdclk(state);
>  	if (ret)
>  		return ret;
> @@ -13577,7 +13588,7 @@ static int intel_atomic_check(struct
> drm_device *dev,
>  	struct intel_crtc_state *old_crtc_state, *new_crtc_state;
>  	struct intel_crtc *crtc;
>  	int ret, i;
> -	bool any_ms = state->cdclk.force_min_cdclk_changed;
> +	bool any_ms = false;
>  
>  	/* Catch I915_MODE_FLAG_INHERITED */
>  	for_each_oldnew_intel_crtc_in_state(state, crtc,
> old_crtc_state,
> @@ -13615,6 +13626,8 @@ static int intel_atomic_check(struct
> drm_device *dev,
>  	if (ret)
>  		goto fail;
>  
> +	any_ms |= state->cdclk.force_min_cdclk_changed;
> +
>  	if (any_ms) {
>  		ret = intel_modeset_checks(state);
>  		if (ret)
> @@ -14234,6 +14247,14 @@ static void intel_atomic_track_fbs(struct
> intel_atomic_state *state)
>  					plane->frontbuffer_bit);
>  }
>  
> +static void assert_global_state_locked(struct drm_i915_private
> *dev_priv)
> +{
> +	struct intel_crtc *crtc;
> +
> +	for_each_intel_crtc(&dev_priv->drm, crtc)
> +		drm_modeset_lock_assert_held(&crtc->base.mutex);
> +}
> +
>  static int intel_atomic_commit(struct drm_device *dev,
>  			       struct drm_atomic_state *_state,
>  			       bool nonblock)
> @@ -14299,7 +14320,9 @@ static int intel_atomic_commit(struct
> drm_device *dev,
>  	intel_shared_dpll_swap_state(state);
>  	intel_atomic_track_fbs(state);
>  
> -	if (state->modeset) {
> +	if (state->global_state_changed) {
> +		assert_global_state_locked(dev_priv);
> +
>  		memcpy(dev_priv->min_cdclk, state->min_cdclk,
>  		       sizeof(state->min_cdclk));
>  		memcpy(dev_priv->min_voltage_level, state-
> >min_voltage_level,
> diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h
> b/drivers/gpu/drm/i915/display/intel_display_types.h
> index 40390d855815..01fed8957ade 100644
> --- a/drivers/gpu/drm/i915/display/intel_display_types.h
> +++ b/drivers/gpu/drm/i915/display/intel_display_types.h
> @@ -506,6 +506,14 @@ struct intel_atomic_state {
>  
>  	bool rps_interactive;
>  
> +	/*
> +	 * active_pipes
> +	 * min_cdclk[]
> +	 * min_voltage_level[]
> +	 * cdclk.*
> +	 */
> +	bool global_state_changed;
> +
>  	/* Gen9+ only */
>  	struct skl_ddb_values wm_results;
>  
> diff --git a/drivers/gpu/drm/i915/i915_drv.h
> b/drivers/gpu/drm/i915/i915_drv.h
> index c46b339064c0..d3e61152d924 100644
> --- a/drivers/gpu/drm/i915/i915_drv.h
> +++ b/drivers/gpu/drm/i915/i915_drv.h
> @@ -1105,13 +1105,14 @@ struct drm_i915_private {
>  	unsigned int fdi_pll_freq;
>  	unsigned int czclk_freq;
>  
> +	/*
> +	 * For reading holding any crtc lock is sufficient,
> +	 * for writing must hold all of them.
> +	 */
>  	struct {
>  		/*
>  		 * The current logical cdclk state.
>  		 * See intel_atomic_state.cdclk.logical
> -		 *
> -		 * For reading holding any crtc lock is sufficient,
> -		 * for writing must hold all of them.
>  		 */
>  		struct intel_cdclk_state logical;
>  		/*
> @@ -1181,6 +1182,10 @@ struct drm_i915_private {
>  	 */
>  	struct mutex dpll_lock;
>  
> +	/*
> +	 * For reading active_pipes, min_cdclk, min_voltage_level
> holding
> +	 * any crtc lock is sufficient, for writing must hold all of
> them.
> +	 */
>  	u8 active_pipes;
>  	/* minimum acceptable cdclk for each pipe */
>  	int min_cdclk[I915_MAX_PIPES];

Patch
diff mbox series

diff --git a/drivers/gpu/drm/i915/display/intel_atomic.c b/drivers/gpu/drm/i915/display/intel_atomic.c
index c5a552a69752..9cd6d2348a1e 100644
--- a/drivers/gpu/drm/i915/display/intel_atomic.c
+++ b/drivers/gpu/drm/i915/display/intel_atomic.c
@@ -429,6 +429,13 @@  void intel_atomic_state_clear(struct drm_atomic_state *s)
 	struct intel_atomic_state *state = to_intel_atomic_state(s);
 	drm_atomic_state_default_clear(&state->base);
 	state->dpll_set = state->modeset = false;
+	state->global_state_changed = false;
+	state->active_pipes = 0;
+	memset(&state->min_cdclk, 0, sizeof(state->min_cdclk));
+	memset(&state->min_voltage_level, 0, sizeof(state->min_voltage_level));
+	memset(&state->cdclk.logical, 0, sizeof(state->cdclk.logical));
+	memset(&state->cdclk.actual, 0, sizeof(state->cdclk.actual));
+	state->cdclk.pipe = INVALID_PIPE;
 }
 
 struct intel_crtc_state *
@@ -442,3 +449,40 @@  intel_atomic_get_crtc_state(struct drm_atomic_state *state,
 
 	return to_intel_crtc_state(crtc_state);
 }
+
+int intel_atomic_lock_global_state(struct intel_atomic_state *state)
+{
+	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
+	struct intel_crtc *crtc;
+
+	state->global_state_changed = true;
+
+	for_each_intel_crtc(&dev_priv->drm, crtc) {
+		int ret;
+
+		ret = drm_modeset_lock(&crtc->base.mutex,
+				       state->base.acquire_ctx);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int intel_atomic_serialize_global_state(struct intel_atomic_state *state)
+{
+	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
+	struct intel_crtc *crtc;
+
+	state->global_state_changed = true;
+
+	for_each_intel_crtc(&dev_priv->drm, crtc) {
+		struct intel_crtc_state *crtc_state;
+
+		crtc_state = intel_atomic_get_crtc_state(&state->base, crtc);
+		if (IS_ERR(crtc_state))
+			return PTR_ERR(crtc_state);
+	}
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/i915/display/intel_atomic.h b/drivers/gpu/drm/i915/display/intel_atomic.h
index 58065d3161a3..49d5cb1b9e0a 100644
--- a/drivers/gpu/drm/i915/display/intel_atomic.h
+++ b/drivers/gpu/drm/i915/display/intel_atomic.h
@@ -16,6 +16,7 @@  struct drm_crtc_state;
 struct drm_device;
 struct drm_i915_private;
 struct drm_property;
+struct intel_atomic_state;
 struct intel_crtc;
 struct intel_crtc_state;
 
@@ -46,4 +47,8 @@  int intel_atomic_setup_scalers(struct drm_i915_private *dev_priv,
 			       struct intel_crtc *intel_crtc,
 			       struct intel_crtc_state *crtc_state);
 
+int intel_atomic_lock_global_state(struct intel_atomic_state *state);
+
+int intel_atomic_serialize_global_state(struct intel_atomic_state *state);
+
 #endif /* __INTEL_ATOMIC_H__ */
diff --git a/drivers/gpu/drm/i915/display/intel_audio.c b/drivers/gpu/drm/i915/display/intel_audio.c
index ed18511befa3..85e6b2bbb34f 100644
--- a/drivers/gpu/drm/i915/display/intel_audio.c
+++ b/drivers/gpu/drm/i915/display/intel_audio.c
@@ -28,6 +28,7 @@ 
 #include <drm/i915_component.h>
 
 #include "i915_drv.h"
+#include "intel_atomic.h"
 #include "intel_audio.h"
 #include "intel_display_types.h"
 #include "intel_lpe_audio.h"
@@ -818,13 +819,8 @@  static void glk_force_audio_cdclk(struct drm_i915_private *dev_priv,
 	to_intel_atomic_state(state)->cdclk.force_min_cdclk =
 		enable ? 2 * 96000 : 0;
 
-	/*
-	 * Protects dev_priv->cdclk.force_min_cdclk
-	 * Need to lock this here in case we have no active pipes
-	 * and thus wouldn't lock it during the commit otherwise.
-	 */
-	ret = drm_modeset_lock(&dev_priv->drm.mode_config.connection_mutex,
-			       &ctx);
+	/* Protects dev_priv->cdclk.force_min_cdclk */
+	ret = intel_atomic_lock_global_state(to_intel_atomic_state(state));
 	if (!ret)
 		ret = drm_atomic_commit(state);
 
diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c
index 6eaebc38ee01..6c17a3bbf866 100644
--- a/drivers/gpu/drm/i915/display/intel_cdclk.c
+++ b/drivers/gpu/drm/i915/display/intel_cdclk.c
@@ -2007,11 +2007,20 @@  static int intel_compute_min_cdclk(struct intel_atomic_state *state)
 	       sizeof(state->min_cdclk));
 
 	for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) {
+		int ret;
+
 		min_cdclk = intel_crtc_compute_min_cdclk(crtc_state);
 		if (min_cdclk < 0)
 			return min_cdclk;
 
+		if (state->min_cdclk[i] == min_cdclk)
+			continue;
+
 		state->min_cdclk[i] = min_cdclk;
+
+		ret = intel_atomic_lock_global_state(state);
+		if (ret)
+			return ret;
 	}
 
 	min_cdclk = state->cdclk.force_min_cdclk;
@@ -2034,7 +2043,7 @@  static int intel_compute_min_cdclk(struct intel_atomic_state *state)
  * future platforms this code will need to be
  * adjusted.
  */
-static u8 bxt_compute_min_voltage_level(struct intel_atomic_state *state)
+static int bxt_compute_min_voltage_level(struct intel_atomic_state *state)
 {
 	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
 	struct intel_crtc *crtc;
@@ -2047,11 +2056,21 @@  static u8 bxt_compute_min_voltage_level(struct intel_atomic_state *state)
 	       sizeof(state->min_voltage_level));
 
 	for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) {
+		int ret;
+
 		if (crtc_state->base.enable)
-			state->min_voltage_level[i] =
-				crtc_state->min_voltage_level;
+			min_voltage_level = crtc_state->min_voltage_level;
 		else
-			state->min_voltage_level[i] = 0;
+			min_voltage_level = 0;
+
+		if (state->min_voltage_level[i] == min_voltage_level)
+			continue;
+
+		state->min_voltage_level[i] = min_voltage_level;
+
+		ret = intel_atomic_lock_global_state(state);
+		if (ret)
+			return ret;
 	}
 
 	min_voltage_level = 0;
@@ -2195,20 +2214,24 @@  static int skl_modeset_calc_cdclk(struct intel_atomic_state *state)
 static int bxt_modeset_calc_cdclk(struct intel_atomic_state *state)
 {
 	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
-	int min_cdclk, cdclk, vco;
+	int min_cdclk, min_voltage_level, cdclk, vco;
 
 	min_cdclk = intel_compute_min_cdclk(state);
 	if (min_cdclk < 0)
 		return min_cdclk;
 
+	min_voltage_level = bxt_compute_min_voltage_level(state);
+	if (min_voltage_level < 0)
+		return min_voltage_level;
+
 	cdclk = bxt_calc_cdclk(dev_priv, min_cdclk);
 	vco = bxt_calc_cdclk_pll_vco(dev_priv, cdclk);
 
 	state->cdclk.logical.vco = vco;
 	state->cdclk.logical.cdclk = cdclk;
 	state->cdclk.logical.voltage_level =
-		max(dev_priv->display.calc_voltage_level(cdclk),
-		    bxt_compute_min_voltage_level(state));
+		max_t(int, min_voltage_level,
+		      dev_priv->display.calc_voltage_level(cdclk));
 
 	if (!state->active_pipes) {
 		cdclk = bxt_calc_cdclk(dev_priv, state->cdclk.force_min_cdclk);
@@ -2225,23 +2248,6 @@  static int bxt_modeset_calc_cdclk(struct intel_atomic_state *state)
 	return 0;
 }
 
-static int intel_lock_all_pipes(struct intel_atomic_state *state)
-{
-	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
-	struct intel_crtc *crtc;
-
-	/* Add all pipes to the state */
-	for_each_intel_crtc(&dev_priv->drm, crtc) {
-		struct intel_crtc_state *crtc_state;
-
-		crtc_state = intel_atomic_get_crtc_state(&state->base, crtc);
-		if (IS_ERR(crtc_state))
-			return PTR_ERR(crtc_state);
-	}
-
-	return 0;
-}
-
 static int intel_modeset_all_pipes(struct intel_atomic_state *state)
 {
 	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
@@ -2308,46 +2314,56 @@  int intel_modeset_calc_cdclk(struct intel_atomic_state *state)
 		return ret;
 
 	/*
-	 * Writes to dev_priv->cdclk.logical must protected by
-	 * holding all the crtc locks, even if we don't end up
+	 * Writes to dev_priv->cdclk.{actual,logical} must protected
+	 * by holding all the crtc mutexes even if we don't end up
 	 * touching the hardware
 	 */
-	if (intel_cdclk_changed(&dev_priv->cdclk.logical,
-				&state->cdclk.logical)) {
-		ret = intel_lock_all_pipes(state);
-		if (ret < 0)
+	if (intel_cdclk_changed(&dev_priv->cdclk.actual,
+				&state->cdclk.actual)) {
+		/*
+		 * Also serialize commits across all crtcs
+		 * if the actual hw needs to be poked.
+		 */
+		ret = intel_atomic_serialize_global_state(state);
+		if (ret)
+			return ret;
+	} else if (intel_cdclk_changed(&dev_priv->cdclk.logical,
+				       &state->cdclk.logical)) {
+		ret = intel_atomic_lock_global_state(state);
+		if (ret)
 			return ret;
+	} else {
+		return 0;
 	}
 
-	if (is_power_of_2(state->active_pipes)) {
+	if (is_power_of_2(state->active_pipes) &&
+	    intel_cdclk_needs_cd2x_update(dev_priv,
+					  &dev_priv->cdclk.actual,
+					  &state->cdclk.actual)) {
 		struct intel_crtc *crtc;
 		struct intel_crtc_state *crtc_state;
 
 		pipe = ilog2(state->active_pipes);
 		crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
-		crtc_state = intel_atomic_get_new_crtc_state(state, crtc);
-		if (crtc_state &&
-		    drm_atomic_crtc_needs_modeset(&crtc_state->base))
+
+		crtc_state = intel_atomic_get_crtc_state(&state->base, crtc);
+		if (IS_ERR(crtc_state))
+			return PTR_ERR(crtc_state);
+
+		if (drm_atomic_crtc_needs_modeset(&crtc_state->base))
 			pipe = INVALID_PIPE;
 	} else {
 		pipe = INVALID_PIPE;
 	}
 
-	/* All pipes must be switched off while we change the cdclk. */
-	if (pipe != INVALID_PIPE &&
-	    intel_cdclk_needs_cd2x_update(dev_priv,
-					  &dev_priv->cdclk.actual,
-					  &state->cdclk.actual)) {
-		ret = intel_lock_all_pipes(state);
-		if (ret)
-			return ret;
-
+	if (pipe != INVALID_PIPE) {
 		state->cdclk.pipe = pipe;
 
 		DRM_DEBUG_KMS("Can change cdclk with pipe %c active\n",
 			      pipe_name(pipe));
 	} else if (intel_cdclk_needs_modeset(&dev_priv->cdclk.actual,
 					     &state->cdclk.actual)) {
+		/* All pipes must be switched off while we change the cdclk. */
 		ret = intel_modeset_all_pipes(state);
 		if (ret)
 			return ret;
diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
index 7d7d1859775a..a8444d9841c1 100644
--- a/drivers/gpu/drm/i915/display/intel_display.c
+++ b/drivers/gpu/drm/i915/display/intel_display.c
@@ -12191,6 +12191,12 @@  static bool check_digital_port_conflicts(struct intel_atomic_state *state)
 	unsigned int used_mst_ports = 0;
 	bool ret = true;
 
+	/*
+	 * We're going to peek into connector->state,
+	 * hence connection_mutex must be held.
+	 */
+	drm_modeset_lock_assert_held(&dev->mode_config.connection_mutex);
+
 	/*
 	 * Walk the connector list instead of the encoder
 	 * list to detect the problem on ddi platforms
@@ -13463,7 +13469,6 @@  static int intel_modeset_checks(struct intel_atomic_state *state)
 	state->active_pipes = dev_priv->active_pipes;
 	state->cdclk.logical = dev_priv->cdclk.logical;
 	state->cdclk.actual = dev_priv->cdclk.actual;
-	state->cdclk.pipe = INVALID_PIPE;
 
 	for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state,
 					    new_crtc_state, i) {
@@ -13476,6 +13481,12 @@  static int intel_modeset_checks(struct intel_atomic_state *state)
 			state->active_pipe_changes |= BIT(crtc->pipe);
 	}
 
+	if (state->active_pipe_changes) {
+		ret = intel_atomic_lock_global_state(state);
+		if (ret)
+			return ret;
+	}
+
 	ret = intel_modeset_calc_cdclk(state);
 	if (ret)
 		return ret;
@@ -13577,7 +13588,7 @@  static int intel_atomic_check(struct drm_device *dev,
 	struct intel_crtc_state *old_crtc_state, *new_crtc_state;
 	struct intel_crtc *crtc;
 	int ret, i;
-	bool any_ms = state->cdclk.force_min_cdclk_changed;
+	bool any_ms = false;
 
 	/* Catch I915_MODE_FLAG_INHERITED */
 	for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state,
@@ -13615,6 +13626,8 @@  static int intel_atomic_check(struct drm_device *dev,
 	if (ret)
 		goto fail;
 
+	any_ms |= state->cdclk.force_min_cdclk_changed;
+
 	if (any_ms) {
 		ret = intel_modeset_checks(state);
 		if (ret)
@@ -14234,6 +14247,14 @@  static void intel_atomic_track_fbs(struct intel_atomic_state *state)
 					plane->frontbuffer_bit);
 }
 
+static void assert_global_state_locked(struct drm_i915_private *dev_priv)
+{
+	struct intel_crtc *crtc;
+
+	for_each_intel_crtc(&dev_priv->drm, crtc)
+		drm_modeset_lock_assert_held(&crtc->base.mutex);
+}
+
 static int intel_atomic_commit(struct drm_device *dev,
 			       struct drm_atomic_state *_state,
 			       bool nonblock)
@@ -14299,7 +14320,9 @@  static int intel_atomic_commit(struct drm_device *dev,
 	intel_shared_dpll_swap_state(state);
 	intel_atomic_track_fbs(state);
 
-	if (state->modeset) {
+	if (state->global_state_changed) {
+		assert_global_state_locked(dev_priv);
+
 		memcpy(dev_priv->min_cdclk, state->min_cdclk,
 		       sizeof(state->min_cdclk));
 		memcpy(dev_priv->min_voltage_level, state->min_voltage_level,
diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
index 40390d855815..01fed8957ade 100644
--- a/drivers/gpu/drm/i915/display/intel_display_types.h
+++ b/drivers/gpu/drm/i915/display/intel_display_types.h
@@ -506,6 +506,14 @@  struct intel_atomic_state {
 
 	bool rps_interactive;
 
+	/*
+	 * active_pipes
+	 * min_cdclk[]
+	 * min_voltage_level[]
+	 * cdclk.*
+	 */
+	bool global_state_changed;
+
 	/* Gen9+ only */
 	struct skl_ddb_values wm_results;
 
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index c46b339064c0..d3e61152d924 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -1105,13 +1105,14 @@  struct drm_i915_private {
 	unsigned int fdi_pll_freq;
 	unsigned int czclk_freq;
 
+	/*
+	 * For reading holding any crtc lock is sufficient,
+	 * for writing must hold all of them.
+	 */
 	struct {
 		/*
 		 * The current logical cdclk state.
 		 * See intel_atomic_state.cdclk.logical
-		 *
-		 * For reading holding any crtc lock is sufficient,
-		 * for writing must hold all of them.
 		 */
 		struct intel_cdclk_state logical;
 		/*
@@ -1181,6 +1182,10 @@  struct drm_i915_private {
 	 */
 	struct mutex dpll_lock;
 
+	/*
+	 * For reading active_pipes, min_cdclk, min_voltage_level holding
+	 * any crtc lock is sufficient, for writing must hold all of them.
+	 */
 	u8 active_pipes;
 	/* minimum acceptable cdclk for each pipe */
 	int min_cdclk[I915_MAX_PIPES];