diff mbox

[2/2] drm/i915: Lane count change detection

Message ID 1463120623-3585-2-git-send-email-shubhangi.shrivastava@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Shubhangi Shrivastava May 13, 2016, 6:23 a.m. UTC
DP panel can issue short pulse interrupts during which it can
modify the number of lanes supported by it. This will result in
change in capabilities of the display and limit the max
resoultion possible on it. This should be informed to the
user mode as well since HWC will issue the modeset assuming
old capabilities.

This patch detects lane count change and simulates a detatch
and attach, so to the user mode or anyone else tracking the
display it will appear as unplug and replug of different
display.

v2: moved queuing of delayed thread to intel_dp where we
set simulate_disconnect_connect flag. There is a chance
for short pulse to be hit even before long pulse detection
is complete, in such a scenario the delayed thread won't
be queued resulting in flag never getting cleared.
Since we set the flag and queue it immediately we can be
sure that the flag will be cleared.

Signed-off-by: Sivakumar Thulasimani <sivakumar.thulasimani@intel.com>
Signed-off-by: Shubhangi Shrivastava <shubhangi.shrivastava@intel.com>
---
 drivers/gpu/drm/i915/i915_drv.h      |  1 +
 drivers/gpu/drm/i915/i915_irq.c      |  2 ++
 drivers/gpu/drm/i915/intel_dp.c      | 47 ++++++++++++++++++++++++++++++---
 drivers/gpu/drm/i915/intel_drv.h     |  4 +++
 drivers/gpu/drm/i915/intel_hotplug.c | 50 ++++++++++++++++++++++++++++++++++++
 5 files changed, 100 insertions(+), 4 deletions(-)

Comments

Daniel Vetter May 17, 2016, 10:17 a.m. UTC | #1
On Fri, May 13, 2016 at 11:53:43AM +0530, Shubhangi Shrivastava wrote:
> DP panel can issue short pulse interrupts during which it can
> modify the number of lanes supported by it. This will result in
> change in capabilities of the display and limit the max
> resoultion possible on it. This should be informed to the
> user mode as well since HWC will issue the modeset assuming
> old capabilities.
> 
> This patch detects lane count change and simulates a detatch
> and attach, so to the user mode or anyone else tracking the
> display it will appear as unplug and replug of different
> display.
> 
> v2: moved queuing of delayed thread to intel_dp where we
> set simulate_disconnect_connect flag. There is a chance
> for short pulse to be hit even before long pulse detection
> is complete, in such a scenario the delayed thread won't
> be queued resulting in flag never getting cleared.
> Since we set the flag and queue it immediately we can be
> sure that the flag will be cleared.
> 
> Signed-off-by: Sivakumar Thulasimani <sivakumar.thulasimani@intel.com>
> Signed-off-by: Shubhangi Shrivastava <shubhangi.shrivastava@intel.com>

This is nonsense, just send a uevent to userspace, and make sure that we
re-validate all the modes. Userspace is supposed to ask for updated mode
lists after every uevent, and then reset a suitable mode.

If hwc can't do that, then we're not going to add a horrible hack to the
kernel of temporarily unplugging something, since fundamentally that's
just duct-tape and still racy.

Note that right now we don't have support to generate uevents for when the
connector status hasn't changed. But we need that in a lot of other cases
too. I think the simplest option is to add a sink_lifetime counter
to drm_connector, and a helper to increment that, e.g.
drm_connector_sink_change().

Then we could call that from our probe/hotplug hooks every time something
relevant changes (different sink dongle, different number of lanes,
different edid). And the top-level probe helpers could also check for any
changes in the sink_lifetime instead of just connect/disconnect. I think
it'd be good if we also wire this up for edid changes, in the core
function which updates the edid blob.

Cheers, Daniel

> ---
>  drivers/gpu/drm/i915/i915_drv.h      |  1 +
>  drivers/gpu/drm/i915/i915_irq.c      |  2 ++
>  drivers/gpu/drm/i915/intel_dp.c      | 47 ++++++++++++++++++++++++++++++---
>  drivers/gpu/drm/i915/intel_drv.h     |  4 +++
>  drivers/gpu/drm/i915/intel_hotplug.c | 50 ++++++++++++++++++++++++++++++++++++
>  5 files changed, 100 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
> index 7a0b513..30d3636 100644
> --- a/drivers/gpu/drm/i915/i915_drv.h
> +++ b/drivers/gpu/drm/i915/i915_drv.h
> @@ -280,6 +280,7 @@ struct i915_hotplug {
>  	u32 long_port_mask;
>  	u32 short_port_mask;
>  	struct work_struct dig_port_work;
> +	struct delayed_work simulate_work;
>  
>  	/*
>  	 * if we get a HPD irq from DP and a HPD irq from non-DP
> diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c
> index f0d9414..74fe755 100644
> --- a/drivers/gpu/drm/i915/i915_irq.c
> +++ b/drivers/gpu/drm/i915/i915_irq.c
> @@ -4572,6 +4572,8 @@ void intel_irq_init(struct drm_i915_private *dev_priv)
>  
>  	INIT_WORK(&dev_priv->rps.work, gen6_pm_rps_work);
>  	INIT_WORK(&dev_priv->l3_parity.error_work, ivybridge_parity_work);
> +	INIT_DELAYED_WORK(&dev_priv->hotplug.simulate_work,
> +			  intel_hpd_simulate_reconnect_work);
>  
>  	/* Let's track the enabled rps events */
>  	if (IS_VALLEYVIEW(dev_priv))
> diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c
> index d5ed84f..ebd525e 100644
> --- a/drivers/gpu/drm/i915/intel_dp.c
> +++ b/drivers/gpu/drm/i915/intel_dp.c
> @@ -3783,6 +3783,26 @@ update_status:
>  	}
>  }
>  
> +static void intel_dp_update_simulate_detach_info(struct intel_dp *intel_dp)
> +{
> +	struct intel_connector *intel_connector = intel_dp->attached_connector;
> +	struct drm_device *dev = intel_dp_to_dev(intel_dp);
> +	struct drm_i915_private *dev_priv = dev->dev_private;
> +
> +	/*
> +	 * Queue thread only if it is not already done.
> +	 * The flag is cleared inside the callback function
> +	 * hence we can be sure that there is no race condition
> +	 * under which it may remain set.
> +	 */
> +	if (!intel_connector->simulate_disconnect_connect) {
> +		intel_connector->simulate_disconnect_connect = true;
> +		DRM_DEBUG_KMS("Queue simulate work func\n");
> +		mod_delayed_work(system_wq, &dev_priv->hotplug.simulate_work,
> +				 msecs_to_jiffies(100));
> +	}
> +}
> +
>  static int
>  intel_dp_check_mst_status(struct intel_dp *intel_dp)
>  {
> @@ -3893,6 +3913,7 @@ intel_dp_short_pulse(struct intel_dp *intel_dp)
>  	struct drm_device *dev = intel_dp_to_dev(intel_dp);
>  	u8 sink_irq_vector;
>  	u8 old_sink_count = intel_dp->sink_count;
> +	u8 old_lane_count = intel_dp->dpcd[DP_MAX_LANE_COUNT];
>  	bool ret;
>  
>  	/*
> @@ -3924,6 +3945,20 @@ intel_dp_short_pulse(struct intel_dp *intel_dp)
>  			sink_irq_vector &= ~DP_AUTOMATED_TEST_REQUEST;
>  		}
>  
> +		/*
> +		 * If lane count has changed, we need to inform
> +		 * user mode of new capablities, this is done by setting
> +		 * our flag to do a fake disconnect and connect so it
> +		 * will appear to the user mode that a new panel is
> +		 * connected and will use the new capabilties of the
> +		 * panel
> +		 */
> +		if (old_lane_count != intel_dp->dpcd[DP_MAX_LANE_COUNT]) {
> +			DRM_DEBUG_KMS("Lane count changed\n");
> +			intel_dp_update_simulate_detach_info(intel_dp);
> +			return false;
> +		}
> +
>  		if (sink_irq_vector & (DP_CP_IRQ | DP_SINK_SPECIFIC_IRQ))
>  			DRM_DEBUG_DRIVER("CP or sink specific irq unhandled\n");
>  
> @@ -4213,13 +4248,17 @@ intel_dp_long_pulse(struct intel_connector *intel_connector)
>  	intel_display_power_get(to_i915(dev), power_domain);
>  
>  	/* Can't disconnect eDP, but you can close the lid... */
> -	if (is_edp(intel_dp))
> +	if (is_edp(intel_dp)) {
>  		status = edp_detect(intel_dp);
> -	else if (intel_digital_port_connected(to_i915(dev),
> -					      dp_to_dig_port(intel_dp)))
> +	} else if (intel_connector->simulate_disconnect_connect) {
> +		DRM_DEBUG_KMS("Simulating disconnect\n");
> +		status = connector_status_disconnected;
> +	} else if (intel_digital_port_connected(to_i915(dev),
> +					      dp_to_dig_port(intel_dp))) {
>  		status = intel_dp_detect_dpcd(intel_dp);
> -	else
> +	} else {
>  		status = connector_status_disconnected;
> +	}
>  
>  	if (status != connector_status_connected) {
>  		intel_dp->compliance_test_active = 0;
> diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
> index 8daadc5..995f0da 100644
> --- a/drivers/gpu/drm/i915/intel_drv.h
> +++ b/drivers/gpu/drm/i915/intel_drv.h
> @@ -257,6 +257,8 @@ struct intel_connector {
>  	struct edid *edid;
>  	struct edid *detect_edid;
>  
> +	bool simulate_disconnect_connect;
> +
>  	/* since POLL and HPD connectors may use the same HPD line keep the native
>  	   state of connector->polled in case hotplug storm detection changes it */
>  	u8 polled;
> @@ -1420,6 +1422,8 @@ bool intel_hdmi_compute_config(struct intel_encoder *encoder,
>  			       struct intel_crtc_state *pipe_config);
>  void intel_dp_dual_mode_set_tmds_output(struct intel_hdmi *hdmi, bool enable);
>  
> +/* intel_hotplug.c */
> +void intel_hpd_simulate_reconnect_work(struct work_struct *work);
>  
>  /* intel_lvds.c */
>  void intel_lvds_init(struct drm_device *dev);
> diff --git a/drivers/gpu/drm/i915/intel_hotplug.c b/drivers/gpu/drm/i915/intel_hotplug.c
> index 38eeca7..7e346d7 100644
> --- a/drivers/gpu/drm/i915/intel_hotplug.c
> +++ b/drivers/gpu/drm/i915/intel_hotplug.c
> @@ -247,6 +247,55 @@ static bool intel_hpd_irq_event(struct drm_device *dev,
>  	return true;
>  }
>  
> +/*
> + * This function is the second half of logic to perform fake
> + * disconnect-connect. The first half was the connector setting
> + * a flag, returning status as disconnected and queing this function.
> + *
> + * This function uses simulate_disconnect_connect flag to identify the
> + * connector that should be detected again. Since this is executed after
> + * a delay if the panel is still plugged in it will be reported as
> + * connected to user mode
> + */
> +void intel_hpd_simulate_reconnect_work(struct work_struct *work)
> +{
> +	struct drm_i915_private *dev_priv =
> +		container_of(work, struct drm_i915_private,
> +			     hotplug.simulate_work.work);
> +	struct drm_device *dev = dev_priv->dev;
> +	struct intel_encoder *intel_encoder;
> +	struct intel_connector *intel_connector;
> +	struct drm_connector *connector;
> +	struct drm_mode_config *mode_config = &dev->mode_config;
> +	enum port port;
> +
> +	DRM_DEBUG_KMS("\n");
> +	mutex_lock(&mode_config->mutex);
> +
> +	list_for_each_entry(connector, &mode_config->connector_list, head) {
> +		intel_connector = to_intel_connector(connector);
> +		if (!intel_connector->simulate_disconnect_connect)
> +			continue;
> +
> +		intel_connector->simulate_disconnect_connect = false;
> +
> +		if (!intel_connector->encoder)
> +			continue;
> +
> +		intel_encoder = intel_connector->encoder;
> +
> +		port = intel_ddi_get_encoder_port(intel_encoder);
> +
> +		spin_lock_irq(&dev_priv->irq_lock);
> +		dev_priv->hotplug.long_port_mask |= (1<<port);
> +		spin_unlock_irq(&dev_priv->irq_lock);
> +	}
> +
> +	mutex_unlock(&mode_config->mutex);
> +
> +	queue_work(dev_priv->hotplug.dp_wq, &dev_priv->hotplug.dig_port_work);
> +}
> +
>  static void i915_digport_work_func(struct work_struct *work)
>  {
>  	struct drm_i915_private *dev_priv =
> @@ -509,4 +558,5 @@ void intel_hpd_cancel_work(struct drm_i915_private *dev_priv)
>  	cancel_work_sync(&dev_priv->hotplug.dig_port_work);
>  	cancel_work_sync(&dev_priv->hotplug.hotplug_work);
>  	cancel_delayed_work_sync(&dev_priv->hotplug.reenable_work);
> +	cancel_delayed_work_sync(&dev_priv->hotplug.simulate_work);
>  }
> -- 
> 2.6.1
> 
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/intel-gfx
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index 7a0b513..30d3636 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -280,6 +280,7 @@  struct i915_hotplug {
 	u32 long_port_mask;
 	u32 short_port_mask;
 	struct work_struct dig_port_work;
+	struct delayed_work simulate_work;
 
 	/*
 	 * if we get a HPD irq from DP and a HPD irq from non-DP
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c
index f0d9414..74fe755 100644
--- a/drivers/gpu/drm/i915/i915_irq.c
+++ b/drivers/gpu/drm/i915/i915_irq.c
@@ -4572,6 +4572,8 @@  void intel_irq_init(struct drm_i915_private *dev_priv)
 
 	INIT_WORK(&dev_priv->rps.work, gen6_pm_rps_work);
 	INIT_WORK(&dev_priv->l3_parity.error_work, ivybridge_parity_work);
+	INIT_DELAYED_WORK(&dev_priv->hotplug.simulate_work,
+			  intel_hpd_simulate_reconnect_work);
 
 	/* Let's track the enabled rps events */
 	if (IS_VALLEYVIEW(dev_priv))
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c
index d5ed84f..ebd525e 100644
--- a/drivers/gpu/drm/i915/intel_dp.c
+++ b/drivers/gpu/drm/i915/intel_dp.c
@@ -3783,6 +3783,26 @@  update_status:
 	}
 }
 
+static void intel_dp_update_simulate_detach_info(struct intel_dp *intel_dp)
+{
+	struct intel_connector *intel_connector = intel_dp->attached_connector;
+	struct drm_device *dev = intel_dp_to_dev(intel_dp);
+	struct drm_i915_private *dev_priv = dev->dev_private;
+
+	/*
+	 * Queue thread only if it is not already done.
+	 * The flag is cleared inside the callback function
+	 * hence we can be sure that there is no race condition
+	 * under which it may remain set.
+	 */
+	if (!intel_connector->simulate_disconnect_connect) {
+		intel_connector->simulate_disconnect_connect = true;
+		DRM_DEBUG_KMS("Queue simulate work func\n");
+		mod_delayed_work(system_wq, &dev_priv->hotplug.simulate_work,
+				 msecs_to_jiffies(100));
+	}
+}
+
 static int
 intel_dp_check_mst_status(struct intel_dp *intel_dp)
 {
@@ -3893,6 +3913,7 @@  intel_dp_short_pulse(struct intel_dp *intel_dp)
 	struct drm_device *dev = intel_dp_to_dev(intel_dp);
 	u8 sink_irq_vector;
 	u8 old_sink_count = intel_dp->sink_count;
+	u8 old_lane_count = intel_dp->dpcd[DP_MAX_LANE_COUNT];
 	bool ret;
 
 	/*
@@ -3924,6 +3945,20 @@  intel_dp_short_pulse(struct intel_dp *intel_dp)
 			sink_irq_vector &= ~DP_AUTOMATED_TEST_REQUEST;
 		}
 
+		/*
+		 * If lane count has changed, we need to inform
+		 * user mode of new capablities, this is done by setting
+		 * our flag to do a fake disconnect and connect so it
+		 * will appear to the user mode that a new panel is
+		 * connected and will use the new capabilties of the
+		 * panel
+		 */
+		if (old_lane_count != intel_dp->dpcd[DP_MAX_LANE_COUNT]) {
+			DRM_DEBUG_KMS("Lane count changed\n");
+			intel_dp_update_simulate_detach_info(intel_dp);
+			return false;
+		}
+
 		if (sink_irq_vector & (DP_CP_IRQ | DP_SINK_SPECIFIC_IRQ))
 			DRM_DEBUG_DRIVER("CP or sink specific irq unhandled\n");
 
@@ -4213,13 +4248,17 @@  intel_dp_long_pulse(struct intel_connector *intel_connector)
 	intel_display_power_get(to_i915(dev), power_domain);
 
 	/* Can't disconnect eDP, but you can close the lid... */
-	if (is_edp(intel_dp))
+	if (is_edp(intel_dp)) {
 		status = edp_detect(intel_dp);
-	else if (intel_digital_port_connected(to_i915(dev),
-					      dp_to_dig_port(intel_dp)))
+	} else if (intel_connector->simulate_disconnect_connect) {
+		DRM_DEBUG_KMS("Simulating disconnect\n");
+		status = connector_status_disconnected;
+	} else if (intel_digital_port_connected(to_i915(dev),
+					      dp_to_dig_port(intel_dp))) {
 		status = intel_dp_detect_dpcd(intel_dp);
-	else
+	} else {
 		status = connector_status_disconnected;
+	}
 
 	if (status != connector_status_connected) {
 		intel_dp->compliance_test_active = 0;
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 8daadc5..995f0da 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -257,6 +257,8 @@  struct intel_connector {
 	struct edid *edid;
 	struct edid *detect_edid;
 
+	bool simulate_disconnect_connect;
+
 	/* since POLL and HPD connectors may use the same HPD line keep the native
 	   state of connector->polled in case hotplug storm detection changes it */
 	u8 polled;
@@ -1420,6 +1422,8 @@  bool intel_hdmi_compute_config(struct intel_encoder *encoder,
 			       struct intel_crtc_state *pipe_config);
 void intel_dp_dual_mode_set_tmds_output(struct intel_hdmi *hdmi, bool enable);
 
+/* intel_hotplug.c */
+void intel_hpd_simulate_reconnect_work(struct work_struct *work);
 
 /* intel_lvds.c */
 void intel_lvds_init(struct drm_device *dev);
diff --git a/drivers/gpu/drm/i915/intel_hotplug.c b/drivers/gpu/drm/i915/intel_hotplug.c
index 38eeca7..7e346d7 100644
--- a/drivers/gpu/drm/i915/intel_hotplug.c
+++ b/drivers/gpu/drm/i915/intel_hotplug.c
@@ -247,6 +247,55 @@  static bool intel_hpd_irq_event(struct drm_device *dev,
 	return true;
 }
 
+/*
+ * This function is the second half of logic to perform fake
+ * disconnect-connect. The first half was the connector setting
+ * a flag, returning status as disconnected and queing this function.
+ *
+ * This function uses simulate_disconnect_connect flag to identify the
+ * connector that should be detected again. Since this is executed after
+ * a delay if the panel is still plugged in it will be reported as
+ * connected to user mode
+ */
+void intel_hpd_simulate_reconnect_work(struct work_struct *work)
+{
+	struct drm_i915_private *dev_priv =
+		container_of(work, struct drm_i915_private,
+			     hotplug.simulate_work.work);
+	struct drm_device *dev = dev_priv->dev;
+	struct intel_encoder *intel_encoder;
+	struct intel_connector *intel_connector;
+	struct drm_connector *connector;
+	struct drm_mode_config *mode_config = &dev->mode_config;
+	enum port port;
+
+	DRM_DEBUG_KMS("\n");
+	mutex_lock(&mode_config->mutex);
+
+	list_for_each_entry(connector, &mode_config->connector_list, head) {
+		intel_connector = to_intel_connector(connector);
+		if (!intel_connector->simulate_disconnect_connect)
+			continue;
+
+		intel_connector->simulate_disconnect_connect = false;
+
+		if (!intel_connector->encoder)
+			continue;
+
+		intel_encoder = intel_connector->encoder;
+
+		port = intel_ddi_get_encoder_port(intel_encoder);
+
+		spin_lock_irq(&dev_priv->irq_lock);
+		dev_priv->hotplug.long_port_mask |= (1<<port);
+		spin_unlock_irq(&dev_priv->irq_lock);
+	}
+
+	mutex_unlock(&mode_config->mutex);
+
+	queue_work(dev_priv->hotplug.dp_wq, &dev_priv->hotplug.dig_port_work);
+}
+
 static void i915_digport_work_func(struct work_struct *work)
 {
 	struct drm_i915_private *dev_priv =
@@ -509,4 +558,5 @@  void intel_hpd_cancel_work(struct drm_i915_private *dev_priv)
 	cancel_work_sync(&dev_priv->hotplug.dig_port_work);
 	cancel_work_sync(&dev_priv->hotplug.hotplug_work);
 	cancel_delayed_work_sync(&dev_priv->hotplug.reenable_work);
+	cancel_delayed_work_sync(&dev_priv->hotplug.simulate_work);
 }