drm/atomic: Acquire connection_mutex lock in drm_helper_probe_single_connector_modes, v4.

mode_valid() called from drm_helper_probe_single_connector_modes()
may need to look at connector->state because what a valid mode is may
depend on connector properties being set. For example some HDMI modes
might be rejected when a connector property forces the connector
into DVI mode.

Some implementations of detect() already lock all state,
so we have to pass an acquire_ctx to them to prevent a deadlock.

This means changing the function signature of detect() slightly,
and passing the acquire_ctx for locking multiple crtc's.
For the callbacks, it will always be non-zero. To allow callers
not to worry about this, drm_helper_probe_detect_ctx is added
which might handle -EDEADLK for you.

Changes since v1:
- Always set ctx parameter.
Changes since v2:
- Always take connection_mutex when probing.
Changes since v3:
- Remove the ctx from intel_dp_long_pulse, and add
  WARN_ON(!connection_mutex) (danvet)
- Update docs to clarify the locking situation. (danvet)

Signed-off-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Boris Brezillon <boris.brezillon@free-electrons.com>
Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/1491504920-4017-1-git-send-email-maarten.lankhorst@linux.intel.com
This commit is contained in:
Maarten Lankhorst 2017-04-06 20:55:20 +02:00
parent 99748ab64f
commit 6c5ed5ae35
10 changed files with 187 additions and 61 deletions

View File

@ -169,13 +169,74 @@ void drm_kms_helper_poll_enable(struct drm_device *dev)
EXPORT_SYMBOL(drm_kms_helper_poll_enable);
static enum drm_connector_status
drm_connector_detect(struct drm_connector *connector, bool force)
drm_helper_probe_detect_ctx(struct drm_connector *connector, bool force)
{
return connector->funcs->detect ?
connector->funcs->detect(connector, force) :
connector_status_connected;
const struct drm_connector_helper_funcs *funcs = connector->helper_private;
struct drm_modeset_acquire_ctx ctx;
int ret;
drm_modeset_acquire_init(&ctx, 0);
retry:
ret = drm_modeset_lock(&connector->dev->mode_config.connection_mutex, &ctx);
if (!ret) {
if (funcs->detect_ctx)
ret = funcs->detect_ctx(connector, &ctx, force);
else if (connector->funcs->detect)
ret = connector->funcs->detect(connector, force);
else
ret = connector_status_connected;
}
if (ret == -EDEADLK) {
drm_modeset_backoff(&ctx);
goto retry;
}
if (WARN_ON(ret < 0))
ret = connector_status_unknown;
drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx);
return ret;
}
/**
* drm_helper_probe_detect - probe connector status
* @connector: connector to probe
* @ctx: acquire_ctx, or NULL to let this function handle locking.
* @force: Whether destructive probe operations should be performed.
*
* This function calls the detect callbacks of the connector.
* This function returns &drm_connector_status, or
* if @ctx is set, it might also return -EDEADLK.
*/
int
drm_helper_probe_detect(struct drm_connector *connector,
struct drm_modeset_acquire_ctx *ctx,
bool force)
{
const struct drm_connector_helper_funcs *funcs = connector->helper_private;
struct drm_device *dev = connector->dev;
int ret;
if (!ctx)
return drm_helper_probe_detect_ctx(connector, force);
ret = drm_modeset_lock(&dev->mode_config.connection_mutex, ctx);
if (ret)
return ret;
if (funcs->detect_ctx)
return funcs->detect_ctx(connector, ctx, force);
else if (connector->funcs->detect)
return connector->funcs->detect(connector, force);
else
return connector_status_connected;
}
EXPORT_SYMBOL(drm_helper_probe_detect);
/**
* drm_helper_probe_single_connector_modes - get complete set of display modes
* @connector: connector to probe
@ -239,15 +300,27 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
struct drm_display_mode *mode;
const struct drm_connector_helper_funcs *connector_funcs =
connector->helper_private;
int count = 0;
int count = 0, ret;
int mode_flags = 0;
bool verbose_prune = true;
enum drm_connector_status old_status;
struct drm_modeset_acquire_ctx ctx;
WARN_ON(!mutex_is_locked(&dev->mode_config.mutex));
drm_modeset_acquire_init(&ctx, 0);
DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id,
connector->name);
retry:
ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
if (ret == -EDEADLK) {
drm_modeset_backoff(&ctx);
goto retry;
} else
WARN_ON(ret < 0);
/* set all old modes to the stale state */
list_for_each_entry(mode, &connector->modes, head)
mode->status = MODE_STALE;
@ -263,7 +336,15 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
if (connector->funcs->force)
connector->funcs->force(connector);
} else {
connector->status = drm_connector_detect(connector, true);
ret = drm_helper_probe_detect(connector, &ctx, true);
if (ret == -EDEADLK) {
drm_modeset_backoff(&ctx);
goto retry;
} else if (WARN(ret < 0, "Invalid return value %i for connector detection\n", ret))
ret = connector_status_unknown;
connector->status = ret;
}
/*
@ -355,6 +436,9 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
prune:
drm_mode_prune_invalid(dev, &connector->modes, verbose_prune);
drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx);
if (list_empty(&connector->modes))
return 0;
@ -440,7 +524,7 @@ static void output_poll_execute(struct work_struct *work)
repoll = true;
connector->status = drm_connector_detect(connector, false);
connector->status = drm_helper_probe_detect(connector, NULL, false);
if (old_status != connector->status) {
const char *old, *new;
@ -588,7 +672,7 @@ bool drm_helper_hpd_irq_event(struct drm_device *dev)
old_status = connector->status;
connector->status = drm_connector_detect(connector, false);
connector->status = drm_helper_probe_detect(connector, NULL, false);
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n",
connector->base.id,
connector->name,

View File

@ -669,15 +669,16 @@ static const struct dmi_system_id intel_spurious_crt_detect[] = {
{ }
};
static enum drm_connector_status
intel_crt_detect(struct drm_connector *connector, bool force)
static int
intel_crt_detect(struct drm_connector *connector,
struct drm_modeset_acquire_ctx *ctx,
bool force)
{
struct drm_i915_private *dev_priv = to_i915(connector->dev);
struct intel_crt *crt = intel_attached_crt(connector);
struct intel_encoder *intel_encoder = &crt->base;
enum drm_connector_status status;
int status, ret;
struct intel_load_detect_pipe tmp;
struct drm_modeset_acquire_ctx ctx;
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] force=%d\n",
connector->base.id, connector->name,
@ -721,10 +722,9 @@ intel_crt_detect(struct drm_connector *connector, bool force)
goto out;
}
drm_modeset_acquire_init(&ctx, 0);
/* for pre-945g platforms use load detect */
if (intel_get_load_detect_pipe(connector, NULL, &tmp, &ctx)) {
ret = intel_get_load_detect_pipe(connector, NULL, &tmp, ctx);
if (ret > 0) {
if (intel_crt_detect_ddc(connector))
status = connector_status_connected;
else if (INTEL_GEN(dev_priv) < 4)
@ -734,12 +734,11 @@ intel_crt_detect(struct drm_connector *connector, bool force)
status = connector_status_disconnected;
else
status = connector_status_unknown;
intel_release_load_detect_pipe(connector, &tmp, &ctx);
} else
intel_release_load_detect_pipe(connector, &tmp, ctx);
} else if (ret == 0)
status = connector_status_unknown;
drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx);
else if (ret < 0)
status = ret;
out:
intel_display_power_put(dev_priv, intel_encoder->power_domain);
@ -811,7 +810,6 @@ void intel_crt_reset(struct drm_encoder *encoder)
static const struct drm_connector_funcs intel_crt_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.detect = intel_crt_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.late_register = intel_connector_register,
.early_unregister = intel_connector_unregister,
@ -823,6 +821,7 @@ static const struct drm_connector_funcs intel_crt_connector_funcs = {
};
static const struct drm_connector_helper_funcs intel_crt_connector_helper_funcs = {
.detect_ctx = intel_crt_detect,
.mode_valid = intel_crt_mode_valid,
.get_modes = intel_crt_get_modes,
};

View File

@ -9480,10 +9480,10 @@ static int intel_modeset_setup_plane_state(struct drm_atomic_state *state,
return 0;
}
bool intel_get_load_detect_pipe(struct drm_connector *connector,
struct drm_display_mode *mode,
struct intel_load_detect_pipe *old,
struct drm_modeset_acquire_ctx *ctx)
int intel_get_load_detect_pipe(struct drm_connector *connector,
struct drm_display_mode *mode,
struct intel_load_detect_pipe *old,
struct drm_modeset_acquire_ctx *ctx)
{
struct intel_crtc *intel_crtc;
struct intel_encoder *intel_encoder =
@ -9506,10 +9506,7 @@ bool intel_get_load_detect_pipe(struct drm_connector *connector,
old->restore_state = NULL;
retry:
ret = drm_modeset_lock(&config->connection_mutex, ctx);
if (ret)
goto fail;
WARN_ON(!drm_modeset_is_locked(&config->connection_mutex));
/*
* Algorithm gets a little messy:
@ -9659,10 +9656,8 @@ bool intel_get_load_detect_pipe(struct drm_connector *connector,
restore_state = NULL;
}
if (ret == -EDEADLK) {
drm_modeset_backoff(ctx);
goto retry;
}
if (ret == -EDEADLK)
return ret;
return false;
}
@ -15030,6 +15025,7 @@ static void intel_enable_pipe_a(struct drm_device *dev)
struct drm_connector *crt = NULL;
struct intel_load_detect_pipe load_detect_temp;
struct drm_modeset_acquire_ctx *ctx = dev->mode_config.acquire_ctx;
int ret;
/* We can't just switch on the pipe A, we need to set things up with a
* proper mode and output configuration. As a gross hack, enable pipe A
@ -15046,7 +15042,10 @@ static void intel_enable_pipe_a(struct drm_device *dev)
if (!crt)
return;
if (intel_get_load_detect_pipe(crt, NULL, &load_detect_temp, ctx))
ret = intel_get_load_detect_pipe(crt, NULL, &load_detect_temp, ctx);
WARN(ret < 0, "All modeset mutexes are locked, but intel_get_load_detect_pipe failed\n");
if (ret > 0)
intel_release_load_detect_pipe(crt, &load_detect_temp, ctx);
}

View File

@ -4566,7 +4566,7 @@ intel_dp_unset_edid(struct intel_dp *intel_dp)
intel_dp->has_audio = false;
}
static enum drm_connector_status
static int
intel_dp_long_pulse(struct intel_connector *intel_connector)
{
struct drm_connector *connector = &intel_connector->base;
@ -4577,6 +4577,8 @@ intel_dp_long_pulse(struct intel_connector *intel_connector)
enum drm_connector_status status;
u8 sink_irq_vector = 0;
WARN_ON(!drm_modeset_is_locked(&connector->dev->mode_config.connection_mutex));
intel_display_power_get(to_i915(dev), intel_dp->aux_power_domain);
/* Can't disconnect eDP, but you can close the lid... */
@ -4635,14 +4637,7 @@ intel_dp_long_pulse(struct intel_connector *intel_connector)
status = connector_status_disconnected;
goto out;
} else if (connector->status == connector_status_connected) {
/*
* If display was connected already and is still connected
* check links status, there has been known issues of
* link loss triggerring long pulse!!!!
*/
drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
intel_dp_check_link_status(intel_dp);
drm_modeset_unlock(&dev->mode_config.connection_mutex);
goto out;
}
@ -4682,11 +4677,13 @@ intel_dp_long_pulse(struct intel_connector *intel_connector)
return status;
}
static enum drm_connector_status
intel_dp_detect(struct drm_connector *connector, bool force)
static int
intel_dp_detect(struct drm_connector *connector,
struct drm_modeset_acquire_ctx *ctx,
bool force)
{
struct intel_dp *intel_dp = intel_attached_dp(connector);
enum drm_connector_status status = connector->status;
int status = connector->status;
DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
connector->base.id, connector->name);
@ -5014,7 +5011,6 @@ void intel_dp_encoder_reset(struct drm_encoder *encoder)
static const struct drm_connector_funcs intel_dp_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.detect = intel_dp_detect,
.force = intel_dp_force,
.fill_modes = drm_helper_probe_single_connector_modes,
.set_property = intel_dp_set_property,
@ -5027,6 +5023,7 @@ static const struct drm_connector_funcs intel_dp_connector_funcs = {
};
static const struct drm_connector_helper_funcs intel_dp_connector_helper_funcs = {
.detect_ctx = intel_dp_detect,
.get_modes = intel_dp_get_modes,
.mode_valid = intel_dp_mode_valid,
};

View File

@ -1353,10 +1353,10 @@ int ironlake_get_lanes_required(int target_clock, int link_bw, int bpp);
void vlv_wait_port_ready(struct drm_i915_private *dev_priv,
struct intel_digital_port *dport,
unsigned int expected_mask);
bool intel_get_load_detect_pipe(struct drm_connector *connector,
struct drm_display_mode *mode,
struct intel_load_detect_pipe *old,
struct drm_modeset_acquire_ctx *ctx);
int intel_get_load_detect_pipe(struct drm_connector *connector,
struct drm_display_mode *mode,
struct intel_load_detect_pipe *old,
struct drm_modeset_acquire_ctx *ctx);
void intel_release_load_detect_pipe(struct drm_connector *connector,
struct intel_load_detect_pipe *old,
struct drm_modeset_acquire_ctx *ctx);

View File

@ -243,7 +243,8 @@ static bool intel_hpd_irq_event(struct drm_device *dev,
WARN_ON(!mutex_is_locked(&dev->mode_config.mutex));
old_status = connector->status;
connector->status = connector->funcs->detect(connector, false);
connector->status = drm_helper_probe_detect(connector, NULL, false);
if (old_status == connector->status)
return false;

View File

@ -1315,8 +1315,10 @@ static void intel_tv_find_better_format(struct drm_connector *connector)
* Currently this always returns CONNECTOR_STATUS_UNKNOWN, as we need to be sure
* we have a pipe programmed in order to probe the TV.
*/
static enum drm_connector_status
intel_tv_detect(struct drm_connector *connector, bool force)
static int
intel_tv_detect(struct drm_connector *connector,
struct drm_modeset_acquire_ctx *ctx,
bool force)
{
struct drm_display_mode mode;
struct intel_tv *intel_tv = intel_attached_tv(connector);
@ -1331,21 +1333,20 @@ intel_tv_detect(struct drm_connector *connector, bool force)
if (force) {
struct intel_load_detect_pipe tmp;
struct drm_modeset_acquire_ctx ctx;
int ret;
drm_modeset_acquire_init(&ctx, 0);
ret = intel_get_load_detect_pipe(connector, &mode, &tmp, ctx);
if (ret < 0)
return ret;
if (intel_get_load_detect_pipe(connector, &mode, &tmp, &ctx)) {
if (ret > 0) {
type = intel_tv_detect_type(intel_tv, connector);
intel_release_load_detect_pipe(connector, &tmp, &ctx);
intel_release_load_detect_pipe(connector, &tmp, ctx);
status = type < 0 ?
connector_status_disconnected :
connector_status_connected;
} else
status = connector_status_unknown;
drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx);
} else
return connector->status;
@ -1516,7 +1517,6 @@ intel_tv_set_property(struct drm_connector *connector, struct drm_property *prop
static const struct drm_connector_funcs intel_tv_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.detect = intel_tv_detect,
.late_register = intel_connector_register,
.early_unregister = intel_connector_unregister,
.destroy = intel_tv_destroy,
@ -1528,6 +1528,7 @@ static const struct drm_connector_funcs intel_tv_connector_funcs = {
};
static const struct drm_connector_helper_funcs intel_tv_connector_helper_funcs = {
.detect_ctx = intel_tv_detect,
.mode_valid = intel_tv_mode_valid,
.get_modes = intel_tv_get_modes,
};

View File

@ -32,6 +32,7 @@
struct drm_device;
struct drm_connector_helper_funcs;
struct drm_modeset_acquire_ctx;
struct drm_device;
struct drm_crtc;
struct drm_encoder;
@ -378,6 +379,11 @@ struct drm_connector_funcs {
* the helper library vtable purely for historical reasons. The only DRM
* core entry point to probe connector state is @fill_modes.
*
* Note that the helper library will already hold
* &drm_mode_config.connection_mutex. Drivers which need to grab additional
* locks to avoid races with concurrent modeset changes need to use
* &drm_connector_helper_funcs.detect_ctx instead.
*
* RETURNS:
*
* drm_connector_status indicating the connector's status.

View File

@ -67,6 +67,9 @@ int drm_helper_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
int drm_helper_probe_single_connector_modes(struct drm_connector
*connector, uint32_t maxX,
uint32_t maxY);
int drm_helper_probe_detect(struct drm_connector *connector,
struct drm_modeset_acquire_ctx *ctx,
bool force);
void drm_kms_helper_poll_init(struct drm_device *dev);
void drm_kms_helper_poll_fini(struct drm_device *dev);
bool drm_helper_hpd_irq_event(struct drm_device *dev);

View File

@ -747,12 +747,44 @@ struct drm_connector_helper_funcs {
* This callback is used by the probe helpers in e.g.
* drm_helper_probe_single_connector_modes().
*
* To avoid races with concurrent connector state updates, the helper
* libraries always call this with the &drm_mode_config.connection_mutex
* held. Because of this it's safe to inspect &drm_connector->state.
*
* RETURNS:
*
* The number of modes added by calling drm_mode_probed_add().
*/
int (*get_modes)(struct drm_connector *connector);
/**
* @detect_ctx:
*
* Check to see if anything is attached to the connector. The parameter
* force is set to false whilst polling, true when checking the
* connector due to a user request. force can be used by the driver to
* avoid expensive, destructive operations during automated probing.
*
* This callback is optional, if not implemented the connector will be
* considered as always being attached.
*
* This is the atomic version of &drm_connector_funcs.detect.
*
* To avoid races against concurrent connector state updates, the
* helper libraries always call this with ctx set to a valid context,
* and &drm_mode_config.connection_mutex will always be locked with
* the ctx parameter set to this ctx. This allows taking additional
* locks as required.
*
* RETURNS:
*
* &drm_connector_status indicating the connector's status,
* or the error code returned by drm_modeset_lock(), -EDEADLK.
*/
int (*detect_ctx)(struct drm_connector *connector,
struct drm_modeset_acquire_ctx *ctx,
bool force);
/**
* @mode_valid:
*
@ -771,6 +803,10 @@ struct drm_connector_helper_funcs {
* CRTC helpers will not call this function. Drivers therefore must
* still fully validate any mode passed in in a modeset request.
*
* To avoid races with concurrent connector state updates, the helper
* libraries always call this with the &drm_mode_config.connection_mutex
* held. Because of this it's safe to inspect &drm_connector->state.
*
* RETURNS:
*
* Either &drm_mode_status.MODE_OK or one of the failure reasons in &enum