From: Ville Syrjälä ville.syrjala@linux.intel.com
On i965/g4x IIR is edge triggered. So in order for IIR to notice that there is still a pending interrupt we have to force and edge in ISR. For the ISR/IIR pipe event bits we can do that by temporarily clearing all the PIPESTAT enable bits when we ack the status bits. This will force the ISR pipe event bit low, and it can then go back high when we restore the PIPESTAT enable bits.
This avoids the following race: 1. stat = read(PIPESTAT) 2. an enabled PIPESTAT status bit goes high 3. write(PIPESTAT, enable|stat); 4. write(IIR, PIPE_EVENT)
The end result is IIR==0 and ISR!=0. This can lead to nasty vblank wait/flip_done timeouts if another interrupt source doesn't trick us into looking at the PIPESTAT status bits despite the IIR PIPE_EVENT bit being low.
Before i965 IIR was level triggered so this problem can't actually happen there. And curiously VLV/CHV went back to the level triggered scheme as well. But for simplicity we'll use the same i965/g4x compatible code for all platforms.
Cc: stable@vger.kernel.org Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=106033 Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=105225 Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=106030 Signed-off-by: Ville Syrjälä ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/i915_irq.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 2fd92a886789..364e1c85315e 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -1893,9 +1893,17 @@ static void i9xx_pipestat_irq_ack(struct drm_i915_private *dev_priv,
/* * Clear the PIPE*STAT regs before the IIR + * + * Toggle the enable bits to make sure we get an + * edge in the ISR pipe event bit if we don't clear + * all the enabled status bits. Otherwise the edge + * triggered IIR on i965/g4x wouldn't notice that + * an interrupt is still pending. */ - if (pipe_stats[pipe]) - I915_WRITE(reg, enable_mask | pipe_stats[pipe]); + if (pipe_stats[pipe]) { + I915_WRITE(reg, pipe_stats[pipe]); + I915_WRITE(reg, enable_mask); + } } spin_unlock(&dev_priv->irq_lock); }
From: Ville Syrjälä ville.syrjala@linux.intel.com
Just like with PIPESTAT, the edge triggered IIR on i965/g4x also causes problems for hotplug interrupts. To make sure we don't get the IIR port interrupt bit stuck low with the ISR bit high we must force an edge in ISR. We do that by clearing PORT_HOTPLUG_EN temporaryly when we ack PORT_HOTPLUG_STAT.
Cc: stable@vger.kernel.org Signed-off-by: Ville Syrjälä ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/i915_irq.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 364e1c85315e..59250ecbd0d9 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -1998,10 +1998,35 @@ static void valleyview_pipestat_irq_handler(struct drm_i915_private *dev_priv,
static u32 i9xx_hpd_irq_ack(struct drm_i915_private *dev_priv) { - u32 hotplug_status = I915_READ(PORT_HOTPLUG_STAT); + u32 hotplug_status, hotplug_en;
- if (hotplug_status) - I915_WRITE(PORT_HOTPLUG_STAT, hotplug_status); + hotplug_status = I915_READ(PORT_HOTPLUG_STAT); + + if (IS_G4X(dev_priv) || + IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + hotplug_status &= HOTPLUG_INT_STATUS_G4X | + DP_AUX_CHANNEL_MASK_INT_STATUS_G4X; + else + hotplug_status &= HOTPLUG_INT_STATUS_I915; + + if (hotplug_status == 0) + return 0; + + spin_lock(&dev_priv->irq_lock); + + /* + * Toggle all PORT_HOTPLUG_EN bits to make sure we + * get an edge in the ISR port interrupt bit if we + * don't clear all the enabled status bits. Otherwise + * the edge triggered IIR on i965/g4x wouldn't notice + * that an interrupt is still pending. + */ + hotplug_en = I915_READ(PORT_HOTPLUG_EN); + I915_WRITE(PORT_HOTPLUG_EN, 0); + I915_WRITE(PORT_HOTPLUG_STAT, hotplug_status); + I915_WRITE(PORT_HOTPLUG_EN, hotplug_en); + + spin_unlock(&dev_priv->irq_lock);
return hotplug_status; }
Quoting Ville Syrjala (2018-06-11 21:02:56)
From: Ville Syrjälä ville.syrjala@linux.intel.com
Just like with PIPESTAT, the edge triggered IIR on i965/g4x also causes problems for hotplug interrupts. To make sure we don't get the IIR port interrupt bit stuck low with the ISR bit high we must force an edge in ISR. We do that by clearing PORT_HOTPLUG_EN temporaryly when we ack PORT_HOTPLUG_STAT.
Cc: stable@vger.kernel.org Signed-off-by: Ville Syrjälä ville.syrjala@linux.intel.com
Having accepted the first, Reviewed-by: Chris Wilson chris@chris-wilson.co.uk -Chris
On Mon, Jun 11, 2018 at 11:02:56PM +0300, Ville Syrjala wrote:
From: Ville Syrjälä ville.syrjala@linux.intel.com
Just like with PIPESTAT, the edge triggered IIR on i965/g4x also causes problems for hotplug interrupts. To make sure we don't get the IIR port interrupt bit stuck low with the ISR bit high we must force an edge in ISR. We do that by clearing PORT_HOTPLUG_EN temporaryly when we ack PORT_HOTPLUG_STAT.
Cc: stable@vger.kernel.org Signed-off-by: Ville Syrjälä ville.syrjala@linux.intel.com
drivers/gpu/drm/i915/i915_irq.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 364e1c85315e..59250ecbd0d9 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -1998,10 +1998,35 @@ static void valleyview_pipestat_irq_handler(struct drm_i915_private *dev_priv, static u32 i9xx_hpd_irq_ack(struct drm_i915_private *dev_priv) {
- u32 hotplug_status = I915_READ(PORT_HOTPLUG_STAT);
- u32 hotplug_status, hotplug_en;
- if (hotplug_status)
I915_WRITE(PORT_HOTPLUG_STAT, hotplug_status);
- hotplug_status = I915_READ(PORT_HOTPLUG_STAT);
- if (IS_G4X(dev_priv) ||
IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
hotplug_status &= HOTPLUG_INT_STATUS_G4X |
DP_AUX_CHANNEL_MASK_INT_STATUS_G4X;
- else
hotplug_status &= HOTPLUG_INT_STATUS_I915;
- if (hotplug_status == 0)
return 0;
- spin_lock(&dev_priv->irq_lock);
- /*
* Toggle all PORT_HOTPLUG_EN bits to make sure we
* get an edge in the ISR port interrupt bit if we
* don't clear all the enabled status bits. Otherwise
* the edge triggered IIR on i965/g4x wouldn't notice
* that an interrupt is still pending.
*/
- hotplug_en = I915_READ(PORT_HOTPLUG_EN);
- I915_WRITE(PORT_HOTPLUG_EN, 0);
- I915_WRITE(PORT_HOTPLUG_STAT, hotplug_status);
- I915_WRITE(PORT_HOTPLUG_EN, hotplug_en);
Actually this seems to be a bad idea. At least one elk here likes to signal a long pulse on disconnected ports every time I toggle the enable bit in PORT_HOTPLUG_EN. The spec even warns that something like this could happen. Not sure why it specifically happens for ports where nothing is connected. Feels like it should be the other way around.
So looks like I'll need to come up with some other way to guarantee the ISR edge. Can't immediately think of any way apart from clearing PORT_HOTPLUG_STAT in a loop :(
- spin_unlock(&dev_priv->irq_lock);
return hotplug_status; } -- 2.16.4
Intel-gfx mailing list Intel-gfx@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/intel-gfx
From: Ville Syrjälä ville.syrjala@linux.intel.com
Just like with PIPESTAT, the edge triggered IIR on i965/g4x also causes problems for hotplug interrupts. To make sure we don't get the IIR port interrupt bit stuck low with the ISR bit high we must force an edge in ISR. Unfortunately we can't borrow the PIPESTAT trick and toggle the enable bits in PORT_HOTPLUG_EN as that act itself generates hotplug interrupts. Instead we just have to loop until we've cleared PORT_HOTPLUG_STAT, or we just give up and WARN.
v2: Don't frob with PORT_HOTPLUG_EN
Cc: stable@vger.kernel.org Signed-off-by: Ville Syrjälä ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/i915_irq.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index d5aee0b74f4b..55a4d4f3fbb2 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -1998,10 +1998,38 @@ static void valleyview_pipestat_irq_handler(struct drm_i915_private *dev_priv,
static u32 i9xx_hpd_irq_ack(struct drm_i915_private *dev_priv) { - u32 hotplug_status = I915_READ(PORT_HOTPLUG_STAT); + u32 hotplug_status = 0, hotplug_status_mask; + int i; + + if (IS_G4X(dev_priv) || + IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + hotplug_status_mask = HOTPLUG_INT_STATUS_G4X | + DP_AUX_CHANNEL_MASK_INT_STATUS_G4X; + else + hotplug_status_mask = HOTPLUG_INT_STATUS_I915;
- if (hotplug_status) + /* + * We absolutely have to clear all the pending interrupt + * bits in PORT_HOTPLUG_STAT. Otherwise the ISR port + * interrupt bit won't have an edge, and the i965/g4x + * edge triggered IIR will not notice that an interrupt + * is still pending. We can't use PORT_HOTPLUG_EN to + * guarantee the edge as the act of toggling the enable + * bits can itself generate a new hotplug interrupt :( + */ + for (i = 0; i < 10; i++) { + u32 tmp = I915_READ(PORT_HOTPLUG_STAT) & hotplug_status_mask; + + if (tmp == 0) + return hotplug_status; + + hotplug_status |= tmp; I915_WRITE(PORT_HOTPLUG_STAT, hotplug_status); + } + + WARN_ONCE(1, + "PORT_HOTPLUG_STAT did not clear (0x%08x)\n", + I915_READ(PORT_HOTPLUG_STAT));
return hotplug_status; }
On Thu, Jun 14, 2018 at 08:56:25PM +0300, Ville Syrjala wrote:
From: Ville Syrjälä ville.syrjala@linux.intel.com
Just like with PIPESTAT, the edge triggered IIR on i965/g4x also causes problems for hotplug interrupts. To make sure we don't get the IIR port interrupt bit stuck low with the ISR bit high we must force an edge in ISR. Unfortunately we can't borrow the PIPESTAT trick and toggle the enable bits in PORT_HOTPLUG_EN as that act itself generates hotplug interrupts. Instead we just have to loop until we've cleared PORT_HOTPLUG_STAT, or we just give up and WARN.
v2: Don't frob with PORT_HOTPLUG_EN
Cc: stable@vger.kernel.org Signed-off-by: Ville Syrjälä ville.syrjala@linux.intel.com
Ok, not so great that we have to loop, but looks like there's no better way to go about it. Good that the problem was root caused:
Reviewed-by: Imre Deak imre.deak@intel.com
drivers/gpu/drm/i915/i915_irq.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index d5aee0b74f4b..55a4d4f3fbb2 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -1998,10 +1998,38 @@ static void valleyview_pipestat_irq_handler(struct drm_i915_private *dev_priv, static u32 i9xx_hpd_irq_ack(struct drm_i915_private *dev_priv) {
- u32 hotplug_status = I915_READ(PORT_HOTPLUG_STAT);
- u32 hotplug_status = 0, hotplug_status_mask;
- int i;
- if (IS_G4X(dev_priv) ||
IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
hotplug_status_mask = HOTPLUG_INT_STATUS_G4X |
DP_AUX_CHANNEL_MASK_INT_STATUS_G4X;
- else
hotplug_status_mask = HOTPLUG_INT_STATUS_I915;
- if (hotplug_status)
- /*
* We absolutely have to clear all the pending interrupt
* bits in PORT_HOTPLUG_STAT. Otherwise the ISR port
* interrupt bit won't have an edge, and the i965/g4x
* edge triggered IIR will not notice that an interrupt
* is still pending. We can't use PORT_HOTPLUG_EN to
* guarantee the edge as the act of toggling the enable
* bits can itself generate a new hotplug interrupt :(
*/
- for (i = 0; i < 10; i++) {
u32 tmp = I915_READ(PORT_HOTPLUG_STAT) & hotplug_status_mask;
if (tmp == 0)
return hotplug_status;
I915_WRITE(PORT_HOTPLUG_STAT, hotplug_status);hotplug_status |= tmp;
- }
- WARN_ONCE(1,
"PORT_HOTPLUG_STAT did not clear (0x%08x)\n",
I915_READ(PORT_HOTPLUG_STAT));
return hotplug_status; } -- 2.16.4
Intel-gfx mailing list Intel-gfx@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/intel-gfx
From: Ville Syrjälä ville.syrjala@linux.intel.com
Adjust the EIR clearing to cope with the edge triggered IIR on i965/g4x. To guarantee an edge in the ISR master error bit we temporarily mask everything in EMR. As some of the EIR bits can't even be directly cleared we also borrow a trick from i915_clear_error_registers() and permanently mask any bit that remains high. No real thought given to how we might unmask them again once the cause for the error has been clered. I suppose on pre-g4x GPU reset will reinitialize EMR from scratch.
Cc: stable@vger.kernel.org Signed-off-by: Ville Syrjälä ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/i915_irq.c | 105 ++++++++++++++++++++++++++++++++++++---- drivers/gpu/drm/i915/i915_reg.h | 1 - 2 files changed, 96 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 59250ecbd0d9..985a137901fb 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -3087,7 +3087,7 @@ static void i915_clear_error_registers(struct drm_i915_private *dev_priv) */ DRM_DEBUG_DRIVER("EIR stuck: 0x%08x, masking\n", eir); I915_WRITE(EMR, I915_READ(EMR) | eir); - I915_WRITE(IIR, I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT); + I915_WRITE(IIR, I915_MASTER_ERROR_INTERRUPT); } }
@@ -4107,6 +4107,81 @@ static int i8xx_irq_postinstall(struct drm_device *dev) return 0; }
+static void i8xx_error_irq_ack(struct drm_i915_private *dev_priv, + u16 *eir, u16 *eir_stuck) +{ + u16 emr; + + *eir = I915_READ16(EIR); + + if (*eir) + I915_WRITE16(EIR, *eir); + + *eir_stuck = I915_READ16(EIR); + if (*eir_stuck == 0) + return; + + /* + * Toggle all EMR bits to make sure we get an edge + * in the ISR master error bit if we don't clear + * all the EIR bits. Otherwise the edge triggered + * IIR on i965/g4x wouldn't notice that an interrupt + * is still pending. Also some EIR bits can't be + * cleared except by handling the underlying error + * (or by a GPU reset) so we mask any bit that + * remains set. + */ + emr = I915_READ16(EMR); + I915_WRITE16(EMR, 0xffff); + I915_WRITE16(EMR, emr | *eir_stuck); +} + +static void i8xx_error_irq_handler(struct drm_i915_private *dev_priv, + u16 eir, u16 eir_stuck) +{ + DRM_DEBUG("Master Error: EIR 0x%04x\n", eir); + + if (eir_stuck) + DRM_DEBUG_DRIVER("EIR stuck: 0x%04x, masked\n", eir_stuck); +} + +static void i9xx_error_irq_ack(struct drm_i915_private *dev_priv, + u32 *eir, u32 *eir_stuck) +{ + u32 emr; + + *eir = I915_READ(EIR); + + I915_WRITE(EIR, *eir); + + *eir_stuck = I915_READ(EIR); + if (*eir_stuck == 0) + return; + + /* + * Toggle all EMR bits to make sure we get an edge + * in the ISR master error bit if we don't clear + * all the EIR bits. Otherwise the edge triggered + * IIR on i965/g4x wouldn't notice that an interrupt + * is still pending. Also some EIR bits can't be + * cleared except by handling the underlying error + * (or by a GPU reset) so we mask any bit that + * remains set. + */ + emr = I915_READ(EMR); + I915_WRITE(EMR, 0xffffffff); + I915_WRITE(EMR, emr | *eir_stuck); +} + +static void i9xx_error_irq_handler(struct drm_i915_private *dev_priv, + u32 eir, u32 eir_stuck) +{ + DRM_DEBUG("Master Error, EIR 0x%08x\n", eir); + + if (eir_stuck) + DRM_DEBUG_DRIVER("EIR stuck: 0x%08x, masked\n", eir_stuck); +} + static irqreturn_t i8xx_irq_handler(int irq, void *arg) { struct drm_device *dev = arg; @@ -4121,6 +4196,7 @@ static irqreturn_t i8xx_irq_handler(int irq, void *arg)
do { u32 pipe_stats[I915_MAX_PIPES] = {}; + u16 eir = 0, eir_stuck = 0; u16 iir;
iir = I915_READ16(IIR); @@ -4133,13 +4209,16 @@ static irqreturn_t i8xx_irq_handler(int irq, void *arg) * signalled in iir */ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats);
+ if (iir & I915_MASTER_ERROR_INTERRUPT) + i8xx_error_irq_ack(dev_priv, &eir, &eir_stuck); + I915_WRITE16(IIR, iir);
if (iir & I915_USER_INTERRUPT) notify_ring(dev_priv->engine[RCS]);
- if (iir & I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT) - DRM_DEBUG("Command parser error, iir 0x%08x\n", iir); + if (iir & I915_MASTER_ERROR_INTERRUPT) + i8xx_error_irq_handler(dev_priv, eir, eir_stuck);
i8xx_pipestat_irq_handler(dev_priv, iir, pipe_stats); } while (0); @@ -4220,6 +4299,7 @@ static irqreturn_t i915_irq_handler(int irq, void *arg)
do { u32 pipe_stats[I915_MAX_PIPES] = {}; + u32 eir = 0, eir_stuck = 0; u32 hotplug_status = 0; u32 iir;
@@ -4237,13 +4317,16 @@ static irqreturn_t i915_irq_handler(int irq, void *arg) * signalled in iir */ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats);
+ if (iir & I915_MASTER_ERROR_INTERRUPT) + i9xx_error_irq_ack(dev_priv, &eir, &eir_stuck); + I915_WRITE(IIR, iir);
if (iir & I915_USER_INTERRUPT) notify_ring(dev_priv->engine[RCS]);
- if (iir & I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT) - DRM_DEBUG("Command parser error, iir 0x%08x\n", iir); + if (iir & I915_MASTER_ERROR_INTERRUPT) + i9xx_error_irq_handler(dev_priv, eir, eir_stuck);
if (hotplug_status) i9xx_hpd_irq_handler(dev_priv, hotplug_status); @@ -4297,14 +4380,14 @@ static int i965_irq_postinstall(struct drm_device *dev) I915_DISPLAY_PORT_INTERRUPT | I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | I915_DISPLAY_PIPE_B_EVENT_INTERRUPT | - I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT); + I915_MASTER_ERROR_INTERRUPT);
enable_mask = I915_ASLE_INTERRUPT | I915_DISPLAY_PORT_INTERRUPT | I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | I915_DISPLAY_PIPE_B_EVENT_INTERRUPT | - I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT | + I915_MASTER_ERROR_INTERRUPT | I915_USER_INTERRUPT;
if (IS_G4X(dev_priv)) @@ -4364,6 +4447,7 @@ static irqreturn_t i965_irq_handler(int irq, void *arg)
do { u32 pipe_stats[I915_MAX_PIPES] = {}; + u32 eir = 0, eir_stuck = 0; u32 hotplug_status = 0; u32 iir;
@@ -4380,6 +4464,9 @@ static irqreturn_t i965_irq_handler(int irq, void *arg) * signalled in iir */ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats);
+ if (iir & I915_MASTER_ERROR_INTERRUPT) + i9xx_error_irq_ack(dev_priv, &eir, &eir_stuck); + I915_WRITE(IIR, iir);
if (iir & I915_USER_INTERRUPT) @@ -4388,8 +4475,8 @@ static irqreturn_t i965_irq_handler(int irq, void *arg) if (iir & I915_BSD_USER_INTERRUPT) notify_ring(dev_priv->engine[VCS]);
- if (iir & I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT) - DRM_DEBUG("Command parser error, iir 0x%08x\n", iir); + if (iir & I915_MASTER_ERROR_INTERRUPT) + i9xx_error_irq_handler(dev_priv, eir, eir_stuck);
if (hotplug_status) i9xx_hpd_irq_handler(dev_priv, hotplug_status); diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 987def26ce82..50a47753014b 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -2805,7 +2805,6 @@ enum i915_power_well_id { #define I915_DISPLAY_PORT_INTERRUPT (1<<17) #define I915_DISPLAY_PIPE_C_HBLANK_INTERRUPT (1<<16) #define I915_MASTER_ERROR_INTERRUPT (1<<15) -#define I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT (1<<15) #define I915_DISPLAY_PIPE_B_HBLANK_INTERRUPT (1<<14) #define I915_GMCH_THERMAL_SENSOR_EVENT_INTERRUPT (1<<14) /* p-state */ #define I915_DISPLAY_PIPE_A_HBLANK_INTERRUPT (1<<13)
On Mon, Jun 11, 2018 at 11:02:57PM +0300, Ville Syrjala wrote:
From: Ville Syrjälä ville.syrjala@linux.intel.com
Adjust the EIR clearing to cope with the edge triggered IIR on i965/g4x. To guarantee an edge in the ISR master error bit we temporarily mask everything in EMR. As some of the EIR bits can't even be directly cleared we also borrow a trick from i915_clear_error_registers() and permanently mask any bit that remains high. No real thought given to how we might unmask them again once the cause for the error has been clered. I suppose on pre-g4x GPU reset will reinitialize EMR from scratch.
Cc: stable@vger.kernel.org Signed-off-by: Ville Syrjälä ville.syrjala@linux.intel.com
drivers/gpu/drm/i915/i915_irq.c | 105 ++++++++++++++++++++++++++++++++++++---- drivers/gpu/drm/i915/i915_reg.h | 1 - 2 files changed, 96 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 59250ecbd0d9..985a137901fb 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -3087,7 +3087,7 @@ static void i915_clear_error_registers(struct drm_i915_private *dev_priv) */ DRM_DEBUG_DRIVER("EIR stuck: 0x%08x, masking\n", eir); I915_WRITE(EMR, I915_READ(EMR) | eir);
I915_WRITE(IIR, I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT);
}I915_WRITE(IIR, I915_MASTER_ERROR_INTERRUPT);
} @@ -4107,6 +4107,81 @@ static int i8xx_irq_postinstall(struct drm_device *dev) return 0; } +static void i8xx_error_irq_ack(struct drm_i915_private *dev_priv,
u16 *eir, u16 *eir_stuck)
+{
- u16 emr;
- *eir = I915_READ16(EIR);
- if (*eir)
I915_WRITE16(EIR, *eir);
- *eir_stuck = I915_READ16(EIR);
Nit: this could store a new error interrupt after the above read to eir_stuck instead of eir, but we won't lose the event in any case, so:
Reviewed-by: Imre Deak imre.deak@intel.com
- if (*eir_stuck == 0)
return;
- /*
* Toggle all EMR bits to make sure we get an edge
* in the ISR master error bit if we don't clear
* all the EIR bits. Otherwise the edge triggered
* IIR on i965/g4x wouldn't notice that an interrupt
* is still pending. Also some EIR bits can't be
* cleared except by handling the underlying error
* (or by a GPU reset) so we mask any bit that
* remains set.
*/
- emr = I915_READ16(EMR);
- I915_WRITE16(EMR, 0xffff);
- I915_WRITE16(EMR, emr | *eir_stuck);
+}
+static void i8xx_error_irq_handler(struct drm_i915_private *dev_priv,
u16 eir, u16 eir_stuck)
+{
- DRM_DEBUG("Master Error: EIR 0x%04x\n", eir);
- if (eir_stuck)
DRM_DEBUG_DRIVER("EIR stuck: 0x%04x, masked\n", eir_stuck);
+}
+static void i9xx_error_irq_ack(struct drm_i915_private *dev_priv,
u32 *eir, u32 *eir_stuck)
+{
- u32 emr;
- *eir = I915_READ(EIR);
- I915_WRITE(EIR, *eir);
- *eir_stuck = I915_READ(EIR);
- if (*eir_stuck == 0)
return;
- /*
* Toggle all EMR bits to make sure we get an edge
* in the ISR master error bit if we don't clear
* all the EIR bits. Otherwise the edge triggered
* IIR on i965/g4x wouldn't notice that an interrupt
* is still pending. Also some EIR bits can't be
* cleared except by handling the underlying error
* (or by a GPU reset) so we mask any bit that
* remains set.
*/
- emr = I915_READ(EMR);
- I915_WRITE(EMR, 0xffffffff);
- I915_WRITE(EMR, emr | *eir_stuck);
+}
+static void i9xx_error_irq_handler(struct drm_i915_private *dev_priv,
u32 eir, u32 eir_stuck)
+{
- DRM_DEBUG("Master Error, EIR 0x%08x\n", eir);
- if (eir_stuck)
DRM_DEBUG_DRIVER("EIR stuck: 0x%08x, masked\n", eir_stuck);
+}
static irqreturn_t i8xx_irq_handler(int irq, void *arg) { struct drm_device *dev = arg; @@ -4121,6 +4196,7 @@ static irqreturn_t i8xx_irq_handler(int irq, void *arg) do { u32 pipe_stats[I915_MAX_PIPES] = {};
u16 iir;u16 eir = 0, eir_stuck = 0;
iir = I915_READ16(IIR); @@ -4133,13 +4209,16 @@ static irqreturn_t i8xx_irq_handler(int irq, void *arg) * signalled in iir */ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats);
if (iir & I915_MASTER_ERROR_INTERRUPT)
i8xx_error_irq_ack(dev_priv, &eir, &eir_stuck);
- I915_WRITE16(IIR, iir);
if (iir & I915_USER_INTERRUPT) notify_ring(dev_priv->engine[RCS]);
if (iir & I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT)
DRM_DEBUG("Command parser error, iir 0x%08x\n", iir);
if (iir & I915_MASTER_ERROR_INTERRUPT)
i8xx_error_irq_handler(dev_priv, eir, eir_stuck);
i8xx_pipestat_irq_handler(dev_priv, iir, pipe_stats); } while (0); @@ -4220,6 +4299,7 @@ static irqreturn_t i915_irq_handler(int irq, void *arg) do { u32 pipe_stats[I915_MAX_PIPES] = {};
u32 hotplug_status = 0; u32 iir;u32 eir = 0, eir_stuck = 0;
@@ -4237,13 +4317,16 @@ static irqreturn_t i915_irq_handler(int irq, void *arg) * signalled in iir */ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats);
if (iir & I915_MASTER_ERROR_INTERRUPT)
i9xx_error_irq_ack(dev_priv, &eir, &eir_stuck);
- I915_WRITE(IIR, iir);
if (iir & I915_USER_INTERRUPT) notify_ring(dev_priv->engine[RCS]);
if (iir & I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT)
DRM_DEBUG("Command parser error, iir 0x%08x\n", iir);
if (iir & I915_MASTER_ERROR_INTERRUPT)
i9xx_error_irq_handler(dev_priv, eir, eir_stuck);
if (hotplug_status) i9xx_hpd_irq_handler(dev_priv, hotplug_status); @@ -4297,14 +4380,14 @@ static int i965_irq_postinstall(struct drm_device *dev) I915_DISPLAY_PORT_INTERRUPT | I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | I915_DISPLAY_PIPE_B_EVENT_INTERRUPT |
I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT);
I915_MASTER_ERROR_INTERRUPT);
enable_mask = I915_ASLE_INTERRUPT | I915_DISPLAY_PORT_INTERRUPT | I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | I915_DISPLAY_PIPE_B_EVENT_INTERRUPT |
I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT |
I915_USER_INTERRUPT;I915_MASTER_ERROR_INTERRUPT |
if (IS_G4X(dev_priv)) @@ -4364,6 +4447,7 @@ static irqreturn_t i965_irq_handler(int irq, void *arg) do { u32 pipe_stats[I915_MAX_PIPES] = {};
u32 hotplug_status = 0; u32 iir;u32 eir = 0, eir_stuck = 0;
@@ -4380,6 +4464,9 @@ static irqreturn_t i965_irq_handler(int irq, void *arg) * signalled in iir */ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats);
if (iir & I915_MASTER_ERROR_INTERRUPT)
i9xx_error_irq_ack(dev_priv, &eir, &eir_stuck);
- I915_WRITE(IIR, iir);
if (iir & I915_USER_INTERRUPT) @@ -4388,8 +4475,8 @@ static irqreturn_t i965_irq_handler(int irq, void *arg) if (iir & I915_BSD_USER_INTERRUPT) notify_ring(dev_priv->engine[VCS]);
if (iir & I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT)
DRM_DEBUG("Command parser error, iir 0x%08x\n", iir);
if (iir & I915_MASTER_ERROR_INTERRUPT)
i9xx_error_irq_handler(dev_priv, eir, eir_stuck);
if (hotplug_status) i9xx_hpd_irq_handler(dev_priv, hotplug_status); diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 987def26ce82..50a47753014b 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -2805,7 +2805,6 @@ enum i915_power_well_id { #define I915_DISPLAY_PORT_INTERRUPT (1<<17) #define I915_DISPLAY_PIPE_C_HBLANK_INTERRUPT (1<<16) #define I915_MASTER_ERROR_INTERRUPT (1<<15) -#define I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT (1<<15) #define I915_DISPLAY_PIPE_B_HBLANK_INTERRUPT (1<<14) #define I915_GMCH_THERMAL_SENSOR_EVENT_INTERRUPT (1<<14) /* p-state */
#define I915_DISPLAY_PIPE_A_HBLANK_INTERRUPT (1<<13)
2.16.4
Intel-gfx mailing list Intel-gfx@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/intel-gfx
Quoting Ville Syrjala (2018-06-11 21:02:55)
From: Ville Syrjälä ville.syrjala@linux.intel.com
On i965/g4x IIR is edge triggered. So in order for IIR to notice that there is still a pending interrupt we have to force and edge in ISR. For the ISR/IIR pipe event bits we can do that by temporarily clearing all the PIPESTAT enable bits when we ack the status bits. This will force the ISR pipe event bit low, and it can then go back high when we restore the PIPESTAT enable bits.
This avoids the following race:
- stat = read(PIPESTAT)
- an enabled PIPESTAT status bit goes high
- write(PIPESTAT, enable|stat);
- write(IIR, PIPE_EVENT)
The end result is IIR==0 and ISR!=0. This can lead to nasty vblank wait/flip_done timeouts if another interrupt source doesn't trick us into looking at the PIPESTAT status bits despite the IIR PIPE_EVENT bit being low.
Before i965 IIR was level triggered so this problem can't actually happen there. And curiously VLV/CHV went back to the level triggered scheme as well. But for simplicity we'll use the same i965/g4x compatible code for all platforms.
Cc: stable@vger.kernel.org Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=106033 Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=105225 Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=106030 Signed-off-by: Ville Syrjälä ville.syrjala@linux.intel.com
drivers/gpu/drm/i915/i915_irq.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 2fd92a886789..364e1c85315e 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -1893,9 +1893,17 @@ static void i9xx_pipestat_irq_ack(struct drm_i915_private *dev_priv, /* * Clear the PIPE*STAT regs before the IIR
*
* Toggle the enable bits to make sure we get an
* edge in the ISR pipe event bit if we don't clear
* all the enabled status bits. Otherwise the edge
* triggered IIR on i965/g4x wouldn't notice that
* an interrupt is still pending. */
if (pipe_stats[pipe])
I915_WRITE(reg, enable_mask | pipe_stats[pipe]);
if (pipe_stats[pipe]) {
I915_WRITE(reg, pipe_stats[pipe]);
Ack status, disable all.
I915_WRITE(reg, enable_mask);
Enable, an asserted bit should trigger a new edge.
It certainly does as you explain, now I just hope the hw feels the same!
Reviewed-by: Chris Wilson chris@chris-wilson.co.uk -Chris
On Mon, Jun 11, 2018 at 09:53:52PM +0100, Chris Wilson wrote:
Quoting Ville Syrjala (2018-06-11 21:02:55)
From: Ville Syrjälä ville.syrjala@linux.intel.com
On i965/g4x IIR is edge triggered. So in order for IIR to notice that there is still a pending interrupt we have to force and edge in ISR. For the ISR/IIR pipe event bits we can do that by temporarily clearing all the PIPESTAT enable bits when we ack the status bits. This will force the ISR pipe event bit low, and it can then go back high when we restore the PIPESTAT enable bits.
This avoids the following race:
- stat = read(PIPESTAT)
- an enabled PIPESTAT status bit goes high
- write(PIPESTAT, enable|stat);
- write(IIR, PIPE_EVENT)
The end result is IIR==0 and ISR!=0. This can lead to nasty vblank wait/flip_done timeouts if another interrupt source doesn't trick us into looking at the PIPESTAT status bits despite the IIR PIPE_EVENT bit being low.
Before i965 IIR was level triggered so this problem can't actually happen there. And curiously VLV/CHV went back to the level triggered scheme as well. But for simplicity we'll use the same i965/g4x compatible code for all platforms.
Cc: stable@vger.kernel.org Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=106033 Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=105225 Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=106030 Signed-off-by: Ville Syrjälä ville.syrjala@linux.intel.com
drivers/gpu/drm/i915/i915_irq.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 2fd92a886789..364e1c85315e 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -1893,9 +1893,17 @@ static void i9xx_pipestat_irq_ack(struct drm_i915_private *dev_priv, /* * Clear the PIPE*STAT regs before the IIR
*
* Toggle the enable bits to make sure we get an
* edge in the ISR pipe event bit if we don't clear
* all the enabled status bits. Otherwise the edge
* triggered IIR on i965/g4x wouldn't notice that
* an interrupt is still pending. */
if (pipe_stats[pipe])
I915_WRITE(reg, enable_mask | pipe_stats[pipe]);
if (pipe_stats[pipe]) {
I915_WRITE(reg, pipe_stats[pipe]);
Ack status, disable all.
I915_WRITE(reg, enable_mask);
Enable, an asserted bit should trigger a new edge.
It certainly does as you explain, now I just hope the hw feels the same!
Reviewed-by: Chris Wilson chris@chris-wilson.co.uk
s/PIPESTATE/PIPESTAT/ (can't count how many times I've done that one wrong) and pushed to dinq. Thanks for the review.
linux-stable-mirror@lists.linaro.org