Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 05eff845 authored by Keith Packard's avatar Keith Packard Committed by Dave Airlie
Browse files

drm/i915: Always read pipestat in irq_handler



Because we write pipestat before iir, it's possible that a pipestat
interrupt will occur between the pipestat write and the iir write. This
leaves pipestat with an interrupt status not visible in iir. This may cause
an interrupt flood as we never clear the pipestat event.

Signed-off-by: default avatarKeith Packard <keithp@keithp.com>
Signed-off-by: default avatarEric Anholt <eric@anholt.net>
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent 2678d9d6
Loading
Loading
Loading
Loading
+36 −19
Original line number Original line Diff line number Diff line
@@ -170,37 +170,54 @@ irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
	u32 iir, new_iir;
	u32 iir, new_iir;
	u32 pipea_stats, pipeb_stats;
	u32 pipea_stats, pipeb_stats;
	u32 vblank_status;
	u32 vblank_enable;
	int vblank = 0;
	int vblank = 0;
	unsigned long irqflags;
	unsigned long irqflags;
	int irq_received;
	int ret = IRQ_NONE;


	atomic_inc(&dev_priv->irq_received);
	atomic_inc(&dev_priv->irq_received);


	iir = I915_READ(IIR);
	iir = I915_READ(IIR);


	if (iir == 0)
	if (IS_I965G(dev)) {
		return IRQ_NONE;
		vblank_status = I915_START_VBLANK_INTERRUPT_STATUS;
		vblank_enable = PIPE_START_VBLANK_INTERRUPT_ENABLE;
	} else {
		vblank_status = I915_VBLANK_INTERRUPT_STATUS;
		vblank_enable = I915_VBLANK_INTERRUPT_ENABLE;
	}


	do {
	for (;;) {
		pipea_stats = 0;
		irq_received = iir != 0;
		pipeb_stats = 0;

		/*
		/* Can't rely on pipestat interrupt bit in iir as it might
		 * Clear the PIPE(A|B)STAT regs before the IIR
		 * have been cleared after the pipestat interrupt was received.
		 * It doesn't set the bit in iir again, but it still produces
		 * interrupts (for non-MSI).
		 */
		 */
		if (iir & I915_DISPLAY_PIPE_A_EVENT_INTERRUPT) {
		spin_lock_irqsave(&dev_priv->user_irq_lock, irqflags);
		spin_lock_irqsave(&dev_priv->user_irq_lock, irqflags);
		pipea_stats = I915_READ(PIPEASTAT);
		pipea_stats = I915_READ(PIPEASTAT);
		pipeb_stats = I915_READ(PIPEBSTAT);
		/*
		 * Clear the PIPE(A|B)STAT regs before the IIR
		 */
		if (pipea_stats & 0x8000ffff) {
			I915_WRITE(PIPEASTAT, pipea_stats);
			I915_WRITE(PIPEASTAT, pipea_stats);
			spin_unlock_irqrestore(&dev_priv->user_irq_lock,
			irq_received = 1;
					       irqflags);
		}
		}


		if (iir & I915_DISPLAY_PIPE_B_EVENT_INTERRUPT) {
		if (pipeb_stats & 0x8000ffff) {
			spin_lock_irqsave(&dev_priv->user_irq_lock, irqflags);
			pipeb_stats = I915_READ(PIPEBSTAT);
			I915_WRITE(PIPEBSTAT, pipeb_stats);
			I915_WRITE(PIPEBSTAT, pipeb_stats);
			spin_unlock_irqrestore(&dev_priv->user_irq_lock,
			irq_received = 1;
					       irqflags);
		}
		}
		spin_unlock_irqrestore(&dev_priv->user_irq_lock, irqflags);

		if (!irq_received)
			break;

		ret = IRQ_HANDLED;


		I915_WRITE(IIR, iir);
		I915_WRITE(IIR, iir);
		new_iir = I915_READ(IIR); /* Flush posted writes */
		new_iir = I915_READ(IIR); /* Flush posted writes */
@@ -214,12 +231,12 @@ irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
			DRM_WAKEUP(&dev_priv->irq_queue);
			DRM_WAKEUP(&dev_priv->irq_queue);
		}
		}


		if (pipea_stats & I915_VBLANK_INTERRUPT_STATUS) {
		if (pipea_stats & vblank_status) {
			vblank++;
			vblank++;
			drm_handle_vblank(dev, 0);
			drm_handle_vblank(dev, 0);
		}
		}


		if (pipeb_stats & I915_VBLANK_INTERRUPT_STATUS) {
		if (pipeb_stats & vblank_status) {
			vblank++;
			vblank++;
			drm_handle_vblank(dev, 1);
			drm_handle_vblank(dev, 1);
		}
		}
@@ -244,9 +261,9 @@ irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
		 * stray interrupts.
		 * stray interrupts.
		 */
		 */
		iir = new_iir;
		iir = new_iir;
	} while (iir != 0);
	}


	return IRQ_HANDLED;
	return ret;
}
}


static int i915_emit_irq(struct drm_device * dev)
static int i915_emit_irq(struct drm_device * dev)