Don't let the ghost eDP haunt us
Subsystems (2)
cover Don't let the ghost eDP haunt us
From: Paulo Zanoni <[email protected]>
Hi
This week I sent a patch called "drm/i915: propagate errors from intel_dp_init_connector" to fix a Haswell bug that was preventing my machine from booting. Chris provided a nice suggestion for it, so this series should implement his suggestion, but split in a few patches. While writing this code I also spotted a few other things, so there are some patches for them here. Patch 3 is the real bug fix and patches 4 & 5 implement Chris's suggestions. Patch 1 is another bug fix and patches 2, 6, 7 & 8 are my bikesheds for the current code.
Thanks,
Paulo
Paulo Zanoni (8):
drm/i915: don't check encoder at DP connector destroy() drm/i915: extract intel_edp_init_connector
drm/i915: propagate errors from intel_dp_init_connector drm/i915: fix the "ghost eDP" connector unwind path drm/i915: fix the "ghost eDP" encoder unwind path drm/i915: check the return value of intel_dp_i2c_init drm/i915: invert the verbosity of intel_enable_fbc drm/i915: rename intel_dp_destroy to intel_dp_connector_destroy
drivers/gpu/drm/i915/intel_ddi.c | 7 +- drivers/gpu/drm/i915/intel_dp.c | 180 +++++++++++++++++++++++---------------- drivers/gpu/drm/i915/intel_drv.h | 2 +- drivers/gpu/drm/i915/intel_pm.c | 3 +- 4 files changed, 113 insertions(+), 79 deletions(-)
Signature · 1 line
1.8.1.2
PATCH 1/8 drm/i915: don't check encoder at DP connector destroy()
From: Paulo Zanoni <[email protected]>
By the time we call intel_dp_destroy (which destroys the connector) the encoder may have been destroyed already, so if we use it we may be reading some free memory. That happens in drm_mode_config_cleanup() and also inside intel_dp_init_connector() when we detect a ghost eDP.
I also hope this may solve some random memory bugs.
Reported-by kmemcheck.
--- drivers/gpu/drm/i915/intel_dp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
drivers/gpu/drm/i915/intel_dp.c +3 -2
@@ -2683,13 +2683,14 @@ done:static void intel_dp_destroy (struct drm_connector *connector) { -struct intel_dp *intel_dp =intel_attached_dp (connector);struct intel_connector *intel_connector =to_intel_connector (connector);if (!IS_ERR_OR_NULL (intel_connector->edid))kfree (intel_connector->edid); -if (is_edp (intel_dp)) +/* Can't call is_edp() since the encoder may have been destroyed +* already. */ +if (connector->connector_type == DRM_MODE_CONNECTOR_eDP)intel_panel_fini (&intel_connector->panel);drm_sysfs_connector_remove (connector);
Signature · 1 line
1.8.1.2
PATCH 2/8 drm/i915: extract intel_edp_init_connector
In reply to Paulo ZanoniFrom: Paulo Zanoni <[email protected]>
Because intel_dp_init_connector is too big for my poor little brain. No functional changes.
--- drivers/gpu/drm/i915/intel_dp.c | 151 ++++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 69 deletions(-)
drivers/gpu/drm/i915/intel_dp.c +82 -69
@@ -2955,6 +2955,86 @@ intel_dp_init_panel_power_sequencer_registers(struct drm_device *dev, I915_READ(pp_div_reg)); } +static bool intel_edp_init_connector (struct intel_dp *intel_dp, +struct intel_connector *intel_connector) +{ +struct drm_connector *connector = &intel_connector->base; +struct intel_digital_port *intel_dig_port =dp_to_dig_port (intel_dp); +struct drm_device *dev = intel_dig_port->base.base.dev; +struct drm_i915_private *dev_priv = dev->dev_private; +struct drm_display_mode *fixed_mode =NULL ; +struct edp_power_seq power_seq = {0 }; +bool has_dpcd; +struct drm_display_mode *scan; +struct edid *edid; + +if (!is_edp (intel_dp)) +return true; + +intel_dp_init_panel_power_sequencer (dev, intel_dp, &power_seq); + +/* Cache DPCD and EDID for edp. */ +ironlake_edp_panel_vdd_on (intel_dp); + has_dpcd =intel_dp_get_dpcd (intel_dp); +ironlake_edp_panel_vdd_off (intel_dp, false); + +if (has_dpcd) { +if (intel_dp->dpcd[DP_DPCD_REV ] >=0x11 ) + dev_priv->no_aux_handshake = + intel_dp->dpcd[DP_MAX_DOWNSPREAD ] & +DP_NO_AUX_HANDSHAKE_LINK_TRAINING ; + }else { +/* if this fails, presume the device is a ghost */ +DRM_INFO ("failed to retrieve link info, disabling eDP\n" ); +intel_dp_encoder_destroy (&intel_dig_port->base.base); +intel_dp_destroy (connector); +return false; + } + +/* We now know it's not a ghost, init power sequence regs. */ +intel_dp_init_panel_power_sequencer_registers (dev, intel_dp, + &power_seq); + +ironlake_edp_panel_vdd_on (intel_dp); + edid =drm_get_edid (connector, &intel_dp->adapter); +if (edid) { +if (drm_add_edid_modes (connector, edid)) { +drm_mode_connector_update_edid_property (connector, + edid); +drm_edid_to_eld (connector, edid); + }else { +kfree (edid); + edid =ERR_PTR (-EINVAL ); + } + }else { + edid =ERR_PTR (-ENOENT ); + } + intel_connector->edid = edid; + +/* prefer fixed mode from EDID if available */ +list_for_each_entry (scan, &connector->probed_modes, head) { +if ((scan->type &DRM_MODE_TYPE_PREFERRED )) { + fixed_mode =drm_mode_duplicate (dev, scan); +break ; + } + } + +/* fallback to VBT if available for eDP */ +if (!fixed_mode && dev_priv->vbt.lfp_lvds_vbt_mode) { + fixed_mode =drm_mode_duplicate (dev, + dev_priv->vbt.lfp_lvds_vbt_mode); +if (fixed_mode) + fixed_mode->type |=DRM_MODE_TYPE_PREFERRED ; + } + +ironlake_edp_panel_vdd_off (intel_dp, false); + +intel_panel_init (&intel_connector->panel, fixed_mode); +intel_panel_setup_backlight (connector); + +return true; +} +void intel_dp_init_connector (struct intel_digital_port *intel_dig_port,struct intel_connector *intel_connector)
@@ -2964,8 +3044,6 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port,struct intel_encoder *intel_encoder = &intel_dig_port->base;struct drm_device *dev = intel_encoder->base.dev;struct drm_i915_private *dev_priv = dev->dev_private; -struct drm_display_mode *fixed_mode =NULL ; -struct edp_power_seq power_seq = {0 };enum port port = intel_dig_port->port;const char *name =NULL ;int type;
@@ -3066,75 +3144,10 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port,BUG (); } -if (is_edp (intel_dp)) -intel_dp_init_panel_power_sequencer (dev, intel_dp, &power_seq); -intel_dp_i2c_init (intel_dp, intel_connector, name); -/* Cache DPCD and EDID for edp. */ -if (is_edp (intel_dp)) { -bool ret; -struct drm_display_mode *scan; -struct edid *edid; - -ironlake_edp_panel_vdd_on (intel_dp); - ret =intel_dp_get_dpcd (intel_dp); -ironlake_edp_panel_vdd_off (intel_dp, false); - -if (ret) { -if (intel_dp->dpcd[DP_DPCD_REV ] >=0x11 ) - dev_priv->no_aux_handshake = - intel_dp->dpcd[DP_MAX_DOWNSPREAD ] & -DP_NO_AUX_HANDSHAKE_LINK_TRAINING ; - }else { -/* if this fails, presume the device is a ghost */ -DRM_INFO ("failed to retrieve link info, disabling eDP\n" ); -intel_dp_encoder_destroy (&intel_encoder->base); -intel_dp_destroy (connector); -return ; - } - -/* We now know it's not a ghost, init power sequence regs. */ -intel_dp_init_panel_power_sequencer_registers (dev, intel_dp, - &power_seq); - -ironlake_edp_panel_vdd_on (intel_dp); - edid =drm_get_edid (connector, &intel_dp->adapter); -if (edid) { -if (drm_add_edid_modes (connector, edid)) { -drm_mode_connector_update_edid_property (connector, edid); -drm_edid_to_eld (connector, edid); - }else { -kfree (edid); - edid =ERR_PTR (-EINVAL ); - } - }else { - edid =ERR_PTR (-ENOENT ); - } - intel_connector->edid = edid; - -/* prefer fixed mode from EDID if available */ -list_for_each_entry (scan, &connector->probed_modes, head) { -if ((scan->type &DRM_MODE_TYPE_PREFERRED )) { - fixed_mode =drm_mode_duplicate (dev, scan); -break ; - } - } - -/* fallback to VBT if available for eDP */ -if (!fixed_mode && dev_priv->vbt.lfp_lvds_vbt_mode) { - fixed_mode =drm_mode_duplicate (dev, dev_priv->vbt.lfp_lvds_vbt_mode); -if (fixed_mode) - fixed_mode->type |=DRM_MODE_TYPE_PREFERRED ; - } - -ironlake_edp_panel_vdd_off (intel_dp, false); - } - -if (is_edp (intel_dp)) { -intel_panel_init (&intel_connector->panel, fixed_mode); -intel_panel_setup_backlight (connector); - } +if (!intel_edp_init_connector (intel_dp, intel_connector)) +return ;intel_dp_add_properties (intel_dp, connector);
Signature · 1 line
1.8.1.2
PATCH 3/8 drm/i915: propagate errors from intel_dp_init_connector
In reply to Paulo ZanoniFrom: Paulo Zanoni <[email protected]>
In case we detect a "ghost eDP", intel_edp_init_connector frees both the connector and encoder and then returns. On Haswell, intel_ddi_init then tries to use the freed encoder on the HDMI initialization path since the following commit:
commit 21a8e6a4853b2ed39fa4c5188a710f2cf1b92026
Author: Daniel Vetter <[email protected]> Date: Wed Apr 10 23:28:35 2013 +0200
drm/i915: don't setup hdmi for port D edp in ddi_init
So now on intel_ddi_init we check for the "ghost eDP" case and return without trying to initialize HDMI. This way we won't try to read the freed "intel_encoder" struct in the next "if" statement.
--- drivers/gpu/drm/i915/intel_ddi.c | 3 ++- drivers/gpu/drm/i915/intel_dp.c | 6 ++++-- drivers/gpu/drm/i915/intel_drv.h | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-)
drivers/gpu/drm/i915/intel_ddi.c +2 -1
@@ -1356,7 +1356,8 @@ void intel_ddi_init(struct drm_device *dev, enum port port) intel_encoder->cloneable = false; intel_encoder->hot_plug = intel_ddi_hot_plug; -intel_dp_init_connector (intel_dig_port, dp_connector); +if (!intel_dp_init_connector (intel_dig_port, dp_connector)) +return ;if (intel_encoder->type !=INTEL_OUTPUT_EDP ) { hdmi_connector = kzalloc(sizeof(struct intel_connector),
drivers/gpu/drm/i915/intel_dp.c +4 -2
@@ -3035,7 +3035,7 @@ static bool intel_edp_init_connector(struct intel_dp *intel_dp,return true; } -void +bool intel_dp_init_connector (struct intel_digital_port *intel_dig_port,struct intel_connector *intel_connector) {
@@ -3147,7 +3147,7 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port,intel_dp_i2c_init (intel_dp, intel_connector, name);if (!intel_edp_init_connector (intel_dp, intel_connector)) -return ; +return false;intel_dp_add_properties (intel_dp, connector);
@@ -3159,6 +3159,8 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port,u32 temp =I915_READ (PEG_BAND_GAP_DATA );I915_WRITE (PEG_BAND_GAP_DATA , (temp & ~0xf ) |0xd ); } + +return true; }void
drivers/gpu/drm/i915/intel_drv.h +1 -1
@@ -586,7 +586,7 @@ extern bool intel_lvds_init(struct drm_device *dev);extern bool intel_is_dual_link_lvds (struct drm_device *dev);extern void intel_dp_init (struct drm_device *dev,int output_reg,enum port port); -extern void intel_dp_init_connector (struct intel_digital_port *intel_dig_port, +extern bool intel_dp_init_connector (struct intel_digital_port *intel_dig_port,struct intel_connector *intel_connector);extern void intel_dp_init_link_config (struct intel_dp *intel_dp);extern void intel_dp_start_link_train (struct intel_dp *intel_dp);
Signature · 1 line
1.8.1.2
PATCH 4/8 drm/i915: fix the "ghost eDP" connector unwind path
In reply to Paulo ZanoniFrom: Paulo Zanoni <[email protected]>
Because calling intel_dp_destroy inside intel_edp_init_connector is just wrong. This is the initialization path, so we should properly unwind all the initialization through the whole caller stack.
On the intel_dp_destroy function we do the following:
1 - Free edid if it exists
2 - Call intel_panel_fini in case it's eDP
3 - Call drm_sysfs_connector_remove
4 - Call drm_connector_cleanup
5 - Free the connector
And here is how we unwind each specific step:
1 - No need as we still didn't assign anything
2 - No need as we still didn't call intel_panel_init
3 - Call it in the same function that called drm_sysfs_connector_add 4 - Call it in the same function that called drm_connector_init 5 - Free it in the same function that allocated it
--- drivers/gpu/drm/i915/intel_ddi.c | 4 +++- drivers/gpu/drm/i915/intel_dp.c | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-)
drivers/gpu/drm/i915/intel_ddi.c +3 -1
@@ -1356,8 +1356,10 @@ void intel_ddi_init(struct drm_device *dev, enum port port) intel_encoder->cloneable = false; intel_encoder->hot_plug = intel_ddi_hot_plug; -if (!intel_dp_init_connector (intel_dig_port, dp_connector)) +if (!intel_dp_init_connector (intel_dig_port, dp_connector)) { +kfree (dp_connector);return ; + }if (intel_encoder->type !=INTEL_OUTPUT_EDP ) { hdmi_connector = kzalloc(sizeof(struct intel_connector),
drivers/gpu/drm/i915/intel_dp.c +6 -3
@@ -2987,7 +2987,6 @@ static bool intel_edp_init_connector(struct intel_dp *intel_dp,/* if this fails, presume the device is a ghost */ DRM_INFO ("failed to retrieve link info, disabling eDP\n" );intel_dp_encoder_destroy (&intel_dig_port->base.base); -intel_dp_destroy (connector);return false; }
@@ -3146,8 +3145,11 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port,intel_dp_i2c_init (intel_dp, intel_connector, name); -if (!intel_edp_init_connector (intel_dp, intel_connector)) +if (!intel_edp_init_connector (intel_dp, intel_connector)) { +drm_sysfs_connector_remove (connector); +drm_connector_cleanup (connector);return false; + }intel_dp_add_properties (intel_dp, connector);
@@ -3206,5 +3208,6 @@ intel_dp_init(struct drm_device *dev, int output_reg, enum port port) intel_encoder->cloneable = false; intel_encoder->hot_plug = intel_dp_hot_plug; -intel_dp_init_connector (intel_dig_port, intel_connector); +if (!intel_dp_init_connector (intel_dig_port, intel_connector)) +kfree (intel_connector); }
Signature · 1 line
1.8.1.2
PATCH 5/8 drm/i915: fix the "ghost eDP" encoder unwind path
In reply to Paulo ZanoniFrom: Paulo Zanoni <[email protected]>
Because calling intel_dp_encoder_destroy inside intel_edp_init_connector is just wrong. This is the initialization path, so we should properly unwind all the initialization through the whole caller stack.
On the intel_dp_encoder_destroy function we do the following:
1 - Call i2c_del_adapter
2 - Call drm_encoder_cleanup
3 - If edp:
3.1 - Cancel panel_vdd_work
3.2 - Call ironlake_panel_vdd_of_sync
4 - Free the encoder
And here is how we unwind each specific step:
1 - We have intel_dp_init_connector -> intel_dp_i2c_init ->
i2c_dp_aux_add_bus -> i2c_add_adapter, so we call i2c_del_dapter at intel_dp_init_connector
2 - Call it in the same function that called drm_encoder_init 3 - Call it in the same function that called INIT_DELAYED_WORK 4 - Free it in the same function that allocated it
--- drivers/gpu/drm/i915/intel_ddi.c | 2 ++ drivers/gpu/drm/i915/intel_dp.c | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-)
drivers/gpu/drm/i915/intel_ddi.c +2 -0
@@ -1357,6 +1357,8 @@ void intel_ddi_init(struct drm_device *dev, enum port port) intel_encoder->hot_plug = intel_ddi_hot_plug;if (!intel_dp_init_connector (intel_dig_port, dp_connector)) { +drm_encoder_cleanup (encoder); +kfree (intel_dig_port);kfree (dp_connector);return ; }
drivers/gpu/drm/i915/intel_dp.c +11 -2
@@ -2986,7 +2986,6 @@ static bool intel_edp_init_connector(struct intel_dp *intel_dp, }else {/* if this fails, presume the device is a ghost */ DRM_INFO ("failed to retrieve link info, disabling eDP\n" ); -intel_dp_encoder_destroy (&intel_dig_port->base.base);return false; }
@@ -3146,6 +3145,13 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port,intel_dp_i2c_init (intel_dp, intel_connector, name);if (!intel_edp_init_connector (intel_dp, intel_connector)) { +i2c_del_adapter (&intel_dp->adapter); +if (is_edp (intel_dp)) { +cancel_delayed_work_sync (&intel_dp->panel_vdd_work); +mutex_lock (&dev->mode_config.mutex); +ironlake_panel_vdd_off_sync (intel_dp); +mutex_unlock (&dev->mode_config.mutex); + }drm_sysfs_connector_remove (connector);drm_connector_cleanup (connector);return false;
@@ -3208,6 +3214,9 @@ intel_dp_init(struct drm_device *dev, int output_reg, enum port port) intel_encoder->cloneable = false; intel_encoder->hot_plug = intel_dp_hot_plug; -if (!intel_dp_init_connector (intel_dig_port, intel_connector)) +if (!intel_dp_init_connector (intel_dig_port, intel_connector)) { +drm_encoder_cleanup (encoder); +kfree (intel_dig_port);kfree (intel_connector); + } }
Signature · 1 line
1.8.1.2
PATCH 6/8 drm/i915: check the return value of intel_dp_i2c_init
In reply to Paulo ZanoniFrom: Paulo Zanoni <[email protected]>
We've been ignoring this return value, so print a nice backtrace in case it's not what we expected.
--- drivers/gpu/drm/i915/intel_dp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
drivers/gpu/drm/i915/intel_dp.c +4 -2
@@ -3044,7 +3044,7 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port,struct drm_i915_private *dev_priv = dev->dev_private;enum port port = intel_dig_port->port;const char *name =NULL ; -int type; +int type, error;/* Preserve the current hw state. */ intel_dp->DP =I915_READ (intel_dp->output_reg);
@@ -3142,7 +3142,9 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port,BUG (); } -intel_dp_i2c_init (intel_dp, intel_connector, name); + error =intel_dp_i2c_init (intel_dp, intel_connector, name); +WARN (error,"intel_dp_i2c_init failed with error %d for port %c\n" , + error,port_name (port));if (!intel_edp_init_connector (intel_dp, intel_connector)) {i2c_del_adapter (&intel_dp->adapter);
Signature · 1 line
1.8.1.2
PATCH 7/8 drm/i915: invert the verbosity of intel_enable_fbc
In reply to Paulo ZanoniFrom: Paulo Zanoni <[email protected]>
We currently print a DRM_DEBUG_KMS message on the happy path and don't print anything on the "failed to allocate" path. On some desktop environments (e.g., Unity) I see the "scheduling delayed FBC enable" thousands and thousands of times on my dmesg.
So kill the useless message for the happy case, saving a lot of dmesg space, and properly signal the "kzalloc fail" case.
--- drivers/gpu/drm/i915/intel_pm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
drivers/gpu/drm/i915/intel_pm.c +1 -2
@@ -381,6 +381,7 @@ void intel_enable_fbc(struct drm_crtc *crtc, unsigned long interval) work =kzalloc (sizeof *work,GFP_KERNEL );if (work ==NULL ) { +DRM_ERROR ("Failed to allocate FBC work structure\n" ); dev_priv->display.enable_fbc (crtc, interval);return ; }
@@ -392,8 +393,6 @@ void intel_enable_fbc(struct drm_crtc *crtc, unsigned long interval) dev_priv->fbc_work = work; -DRM_DEBUG_KMS ("scheduling delayed FBC enable\n" ); -/* Delay the actual enabling to let pageflipping cease and the * display to settle before starting the compression. Note that * this delay also serves a second purpose: it allows for a
Signature · 1 line
1.8.1.2
PATCH 8/8 drm/i915: rename intel_dp_destroy to intel_dp_connector_destroy
In reply to Paulo ZanoniFrom: Paulo Zanoni <[email protected]>
Because it's the function that destroys the connector, not the encoder. And we already have intel_dp_encoder_destroy.
This has annoyed me for a long time.
--- drivers/gpu/drm/i915/intel_dp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/gpu/drm/i915/intel_dp.c +2 -2
@@ -2681,7 +2681,7 @@ done: }static void -intel_dp_destroy (struct drm_connector *connector) +intel_dp_connector_destroy (struct drm_connector *connector) {struct intel_connector *intel_connector =to_intel_connector (connector);
@@ -2724,7 +2724,7 @@ static const struct drm_connector_funcs intel_dp_connector_funcs = { .detect = intel_dp_detect, .fill_modes = drm_helper_probe_single_connector_modes, .set_property = intel_dp_set_property, - .destroy = intel_dp_destroy, + .destroy = intel_dp_connector_destroy, };static const struct drm_connector_helper_funcs intel_dp_connector_helper_funcs = {
Signature · 1 line
1.8.1.2