diff mbox series

[v2,8/8] drm/i915: Hook up display fault interrupts for VLV/CHV

Message ID 20250217070047.953-9-ville.syrjala@linux.intel.com (mailing list archive)
State New
Headers show
Series drm/i915: Provide more information on display faults | expand

Commit Message

Ville Syrjälä Feb. 17, 2025, 7 a.m. UTC
From: Ville Syrjälä <ville.syrjala@linux.intel.com>

Hook up the display fault irq handlers for VLV/CHV.

Unfortunately the actual hardware doesn't agree with the
spec on how DPINVGTT should behave. The docs claim that
the status bits can be cleared by writing '1' to them,
but in reality there doesn't seem to be any way to clear
them. So we must disable and ignore any fault we've already
seen in the past. The entire register does reset when
the display power well goes down, so we can just always
re-enable all the bits in irq postinstall without having
to track the state beyond that.

v2: Use intel_display instead of dev_priv
    Move xe gen2_error_{init,reset}() out

Reviewed-by: Vinod Govindapillai <vinod.govindapillai@intel.com>
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
---
 .../gpu/drm/i915/display/intel_display_irq.c  | 132 +++++++++++++++++-
 .../gpu/drm/i915/display/intel_display_irq.h  |   3 +
 drivers/gpu/drm/i915/i915_irq.c               |  14 ++
 drivers/gpu/drm/i915/i915_reg.h               |  10 ++
 4 files changed, 158 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/drivers/gpu/drm/i915/display/intel_display_irq.c b/drivers/gpu/drm/i915/display/intel_display_irq.c
index 1fa44f6bf880..99fb7fc7be39 100644
--- a/drivers/gpu/drm/i915/display/intel_display_irq.c
+++ b/drivers/gpu/drm/i915/display/intel_display_irq.c
@@ -1784,6 +1784,114 @@  void bdw_disable_vblank(struct drm_crtc *_crtc)
 		schedule_work(&display->irq.vblank_dc_work);
 }
 
+static u32 vlv_dpinvgtt_pipe_fault_mask(enum pipe pipe)
+{
+	switch (pipe) {
+	case PIPE_A:
+		return SPRITEB_INVALID_GTT_STATUS |
+			SPRITEA_INVALID_GTT_STATUS |
+			PLANEA_INVALID_GTT_STATUS |
+			CURSORA_INVALID_GTT_STATUS;
+	case PIPE_B:
+		return SPRITED_INVALID_GTT_STATUS |
+			SPRITEC_INVALID_GTT_STATUS |
+			PLANEB_INVALID_GTT_STATUS |
+			CURSORB_INVALID_GTT_STATUS;
+	case PIPE_C:
+		return SPRITEF_INVALID_GTT_STATUS |
+			SPRITEE_INVALID_GTT_STATUS |
+			PLANEC_INVALID_GTT_STATUS |
+			CURSORC_INVALID_GTT_STATUS;
+	default:
+		return 0;
+	}
+}
+
+static const struct pipe_fault_handler vlv_pipe_fault_handlers[] = {
+	{ .fault = SPRITEB_INVALID_GTT_STATUS, .handle = handle_plane_fault, .plane_id = PLANE_SPRITE1, },
+	{ .fault = SPRITEA_INVALID_GTT_STATUS, .handle = handle_plane_fault, .plane_id = PLANE_SPRITE0, },
+	{ .fault = PLANEA_INVALID_GTT_STATUS,  .handle = handle_plane_fault, .plane_id = PLANE_PRIMARY, },
+	{ .fault = CURSORA_INVALID_GTT_STATUS, .handle = handle_plane_fault, .plane_id = PLANE_CURSOR,  },
+	{ .fault = SPRITED_INVALID_GTT_STATUS, .handle = handle_plane_fault, .plane_id = PLANE_SPRITE1, },
+	{ .fault = SPRITEC_INVALID_GTT_STATUS, .handle = handle_plane_fault, .plane_id = PLANE_SPRITE0, },
+	{ .fault = PLANEB_INVALID_GTT_STATUS,  .handle = handle_plane_fault, .plane_id = PLANE_PRIMARY, },
+	{ .fault = CURSORB_INVALID_GTT_STATUS, .handle = handle_plane_fault, .plane_id = PLANE_CURSOR,  },
+	{ .fault = SPRITEF_INVALID_GTT_STATUS, .handle = handle_plane_fault, .plane_id = PLANE_SPRITE1, },
+	{ .fault = SPRITEE_INVALID_GTT_STATUS, .handle = handle_plane_fault, .plane_id = PLANE_SPRITE0, },
+	{ .fault = PLANEC_INVALID_GTT_STATUS,  .handle = handle_plane_fault, .plane_id = PLANE_PRIMARY, },
+	{ .fault = CURSORC_INVALID_GTT_STATUS, .handle = handle_plane_fault, .plane_id = PLANE_CURSOR,  },
+	{}
+};
+
+static void vlv_page_table_error_irq_ack(struct intel_display *display, u32 *dpinvgtt)
+{
+	u32 status, enable, tmp;
+
+	tmp = intel_de_read(display, DPINVGTT);
+
+	enable = tmp >> 16;
+	status = tmp & 0xffff;
+
+	/*
+	 * Despite what the docs claim, the status bits seem to get
+	 * stuck permanently (similar the old PGTBL_ER register), so
+	 * we have to disable and ignore them once set. They do get
+	 * reset if the display power well goes down, so no need to
+	 * track the enable mask explicitly.
+	 */
+	*dpinvgtt = status & enable;
+	enable &= ~status;
+
+	/* customary ack+disable then re-enable to guarantee an edge */
+	intel_de_write(display, DPINVGTT, status);
+	intel_de_write(display, DPINVGTT, enable << 16);
+}
+
+static void vlv_page_table_error_irq_handler(struct intel_display *display, u32 dpinvgtt)
+{
+	enum pipe pipe;
+
+	for_each_pipe(display, pipe) {
+		u32 fault_errors;
+
+		fault_errors = dpinvgtt & vlv_dpinvgtt_pipe_fault_mask(pipe);
+		if (fault_errors)
+			intel_pipe_fault_irq_handler(display, vlv_pipe_fault_handlers,
+						     pipe, fault_errors);
+	}
+}
+
+void vlv_display_error_irq_ack(struct intel_display *display,
+			       u32 *eir, u32 *dpinvgtt)
+{
+	u32 emr;
+
+	*eir = intel_de_read(display, VLV_EIR);
+
+	if (*eir & VLV_ERROR_PAGE_TABLE)
+		vlv_page_table_error_irq_ack(display, dpinvgtt);
+
+	intel_de_write(display, VLV_EIR, *eir);
+
+	/*
+	 * 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.
+	 */
+	emr = intel_de_read(display, VLV_EMR);
+	intel_de_write(display, VLV_EMR, 0xffffffff);
+	intel_de_write(display, VLV_EMR, emr);
+}
+
+void vlv_display_error_irq_handler(struct intel_display *display,
+				   u32 eir, u32 dpinvgtt)
+{
+	drm_dbg(display->drm, "Master Error, EIR 0x%08x\n", eir);
+
+	if (eir & VLV_ERROR_PAGE_TABLE)
+		vlv_page_table_error_irq_handler(display, dpinvgtt);
+}
+
 static void _vlv_display_irq_reset(struct drm_i915_private *dev_priv)
 {
 	struct intel_display *display = &dev_priv->display;
@@ -1793,6 +1901,9 @@  static void _vlv_display_irq_reset(struct drm_i915_private *dev_priv)
 	else
 		intel_de_write(display, DPINVGTT, DPINVGTT_STATUS_MASK_VLV);
 
+	gen2_error_reset(to_intel_uncore(display->drm),
+			 VLV_ERROR_REGS);
+
 	i915_hotplug_interrupt_update_locked(dev_priv, 0xffffffff, 0);
 	intel_de_rmw(display, PORT_HOTPLUG_STAT(dev_priv), 0, 0);
 
@@ -1820,6 +1931,12 @@  void i9xx_display_irq_reset(struct drm_i915_private *i915)
 	i9xx_pipestat_irq_reset(i915);
 }
 
+static u32 vlv_error_mask(void)
+{
+	/* TODO enable other errors too? */
+	return VLV_ERROR_PAGE_TABLE;
+}
+
 void vlv_display_irq_postinstall(struct drm_i915_private *dev_priv)
 {
 	struct intel_display *display = &dev_priv->display;
@@ -1830,6 +1947,18 @@  void vlv_display_irq_postinstall(struct drm_i915_private *dev_priv)
 	if (!dev_priv->display.irq.vlv_display_irqs_enabled)
 		return;
 
+	if (IS_CHERRYVIEW(dev_priv))
+		intel_de_write(display, DPINVGTT,
+			       DPINVGTT_STATUS_MASK_CHV |
+			       DPINVGTT_EN_MASK_CHV);
+	else
+		intel_de_write(display, DPINVGTT,
+			       DPINVGTT_STATUS_MASK_VLV |
+			       DPINVGTT_EN_MASK_VLV);
+
+	gen2_error_init(to_intel_uncore(display->drm),
+			VLV_ERROR_REGS, ~vlv_error_mask());
+
 	pipestat_mask = PIPE_CRC_DONE_INTERRUPT_STATUS;
 
 	i915_enable_pipestat(dev_priv, PIPE_A, PIPE_GMBUS_INTERRUPT_STATUS);
@@ -1840,7 +1969,8 @@  void vlv_display_irq_postinstall(struct drm_i915_private *dev_priv)
 		I915_DISPLAY_PIPE_A_EVENT_INTERRUPT |
 		I915_DISPLAY_PIPE_B_EVENT_INTERRUPT |
 		I915_LPE_PIPE_A_INTERRUPT |
-		I915_LPE_PIPE_B_INTERRUPT;
+		I915_LPE_PIPE_B_INTERRUPT |
+		I915_MASTER_ERROR_INTERRUPT;
 
 	if (IS_CHERRYVIEW(dev_priv))
 		enable_mask |= I915_DISPLAY_PIPE_C_EVENT_INTERRUPT |
diff --git a/drivers/gpu/drm/i915/display/intel_display_irq.h b/drivers/gpu/drm/i915/display/intel_display_irq.h
index 75ab38a0908e..d9867cd0a220 100644
--- a/drivers/gpu/drm/i915/display/intel_display_irq.h
+++ b/drivers/gpu/drm/i915/display/intel_display_irq.h
@@ -76,6 +76,9 @@  void i915_pipestat_irq_handler(struct drm_i915_private *i915, u32 iir, u32 pipe_
 void i965_pipestat_irq_handler(struct drm_i915_private *i915, u32 iir, u32 pipe_stats[I915_MAX_PIPES]);
 void valleyview_pipestat_irq_handler(struct drm_i915_private *i915, u32 pipe_stats[I915_MAX_PIPES]);
 
+void vlv_display_error_irq_ack(struct intel_display *display, u32 *eir, u32 *dpinvgtt);
+void vlv_display_error_irq_handler(struct intel_display *display, u32 eir, u32 dpinvgtt);
+
 void intel_display_irq_init(struct drm_i915_private *i915);
 
 void i915gm_irq_cstate_wa(struct drm_i915_private *i915, bool enable);
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c
index da130e1b5cc1..37ca4a35daf2 100644
--- a/drivers/gpu/drm/i915/i915_irq.c
+++ b/drivers/gpu/drm/i915/i915_irq.c
@@ -241,6 +241,7 @@  static irqreturn_t valleyview_irq_handler(int irq, void *arg)
 
 	do {
 		u32 iir, gt_iir, pm_iir;
+		u32 eir = 0, dpinvgtt = 0;
 		u32 pipe_stats[I915_MAX_PIPES] = {};
 		u32 hotplug_status = 0;
 		u32 ier = 0;
@@ -278,6 +279,9 @@  static irqreturn_t valleyview_irq_handler(int irq, void *arg)
 		if (iir & I915_DISPLAY_PORT_INTERRUPT)
 			hotplug_status = i9xx_hpd_irq_ack(dev_priv);
 
+		if (iir & I915_MASTER_ERROR_INTERRUPT)
+			vlv_display_error_irq_ack(display, &eir, &dpinvgtt);
+
 		/* Call regardless, as some status bits might not be
 		 * signalled in IIR */
 		i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats);
@@ -304,6 +308,9 @@  static irqreturn_t valleyview_irq_handler(int irq, void *arg)
 		if (hotplug_status)
 			i9xx_hpd_irq_handler(dev_priv, hotplug_status);
 
+		if (iir & I915_MASTER_ERROR_INTERRUPT)
+			vlv_display_error_irq_handler(display, eir, dpinvgtt);
+
 		valleyview_pipestat_irq_handler(dev_priv, pipe_stats);
 	} while (0);
 
@@ -328,6 +335,7 @@  static irqreturn_t cherryview_irq_handler(int irq, void *arg)
 
 	do {
 		u32 master_ctl, iir;
+		u32 eir = 0, dpinvgtt = 0;
 		u32 pipe_stats[I915_MAX_PIPES] = {};
 		u32 hotplug_status = 0;
 		u32 ier = 0;
@@ -361,6 +369,9 @@  static irqreturn_t cherryview_irq_handler(int irq, void *arg)
 		if (iir & I915_DISPLAY_PORT_INTERRUPT)
 			hotplug_status = i9xx_hpd_irq_ack(dev_priv);
 
+		if (iir & I915_MASTER_ERROR_INTERRUPT)
+			vlv_display_error_irq_ack(display, &eir, &dpinvgtt);
+
 		/* Call regardless, as some status bits might not be
 		 * signalled in IIR */
 		i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats);
@@ -383,6 +394,9 @@  static irqreturn_t cherryview_irq_handler(int irq, void *arg)
 		if (hotplug_status)
 			i9xx_hpd_irq_handler(dev_priv, hotplug_status);
 
+		if (iir & I915_MASTER_ERROR_INTERRUPT)
+			vlv_display_error_irq_handler(display, eir, dpinvgtt);
+
 		valleyview_pipestat_irq_handler(dev_priv, pipe_stats);
 	} while (0);
 
diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index be1aab838be9..b31b26e9a685 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -475,6 +475,16 @@ 
 
 #define GEN2_ERROR_REGS		I915_ERROR_REGS(EMR, EIR)
 
+#define VLV_EIR		_MMIO(VLV_DISPLAY_BASE + 0x20b0)
+#define VLV_EMR		_MMIO(VLV_DISPLAY_BASE + 0x20b4)
+#define VLV_ESR		_MMIO(VLV_DISPLAY_BASE + 0x20b8)
+#define   VLV_ERROR_GUNIT_TLB_DATA			(1 << 6)
+#define   VLV_ERROR_GUNIT_TLB_PTE			(1 << 5)
+#define   VLV_ERROR_PAGE_TABLE				(1 << 4)
+#define   VLV_ERROR_CLAIM				(1 << 0)
+
+#define VLV_ERROR_REGS		I915_ERROR_REGS(VLV_EMR, VLV_EIR)
+
 #define INSTPM	        _MMIO(0x20c0)
 #define   INSTPM_SELF_EN (1 << 12) /* 915GM only */
 #define   INSTPM_AGPBUSY_INT_EN (1 << 11) /* gen3: when disabled, pending interrupts