Hi Maarten,

I tested patches 05/24 and 06/24 from your i915-rt v6 series (cherry-picked 
onto 6.12.85, Debian 13) on a ThinkPad 13 2nd Gen (20J2S0PE00), Intel i3-7100U, 
HD Graphics 620, eDP panel.

Before the patches (enable_psr=1): intermittent hard locks on lid-open event, 
requiring EC power cycle. Journal showed:

i915 0000:00:02.0: [drm] *ERROR* Atomic update failure on pipe A

(start=54409 end=54410) time 137 us, min 1073, max 1079,

scanline start 1071, end 1081

System unrecoverable after this event.
After the patches (enable_psr=1, ~3 hours runtime): one atomic update failure 
observed, system survived and continued normally:

i915 0000:00:02.0: [drm] *ERROR* Atomic update failure on pipe A

(start=70704 end=70705) time 156 us, min 1073, max 1079,

scanline start 1072, end 1073

The violation narrowed from 2 scanlines early / 10 scanlines into active region 
(fatal) to 1 scanline early / 1 scanline into active region (survivable). No 
further hard locks observed so far.

Is the remaining 1-scanline jitter expected to be addressed by later patches in 
the series, or is there a separate timing issue on this hardware worth 
investigating?

I am attaching the exact patchfile applied against unpacked kernel sources as 
provided by Debian themselves. steps to reproduce:

apt-get install linux-source-6.12
cd /usr/src;tar xf linux-source-6.12.tar.xz
cd linux-source-6.12/
patch -p1 < /tmp/psr.patch
cp /usr/src/linux-config-6.12/config.amd64_none_amd64.xz .config.xz
unxz .config.xz
make oldconfig
make -j4 bindeb-pkg LOCALVERSION=-psr-fix

(...)
dpkg -i ../linux-image-6.12.85-psr-fix_6.12.85-2_amd64.deb
dpkg -i ../linux-headers-6.12.85-psr-fix_6.12.85-2_amd64.deb
[reboot]

dokl@dt13:~$ uname -r

6.12.85-psr-fix
dokl@dt13:~$ sudo cat /sys/module/i915/parameters/enable_psr
1

I'm happy to test further patches or provide additional diagnostics if useful. 
The hardware is available for regular testing.

Note: this unit has evaluation/non-retail firmware (Phoenix SecureCore, build 
09/13/22, "FOR EVALUATION ONLY. NOT FOR RESALE"), which may affect ACPI/EC 
behavior compared to retail units.

Tested-by: Dominik Klonowski 
[<[email protected]>](mailto:[email protected])
Hardware: ThinkPad 13 2nd Gen 20J2S0PE00, i3-7100U, HD Graphics 620
Kernel: 6.12.85+debian13, patches 05+06 of i915-rt v6 cherry-picked
--- a/drivers/gpu/drm/i915/display/intel_vblank.h	2026-05-10 08:34:18.742734822 +0000
+++ b/drivers/gpu/drm/i915/display/intel_vblank.h	2026-05-10 08:34:18.747610505 +0000
@@ -36,6 +36,7 @@
 bool intel_crtc_get_vblank_timestamp(struct drm_crtc *crtc, int *max_error,
 				     ktime_t *vblank_time, bool in_vblank_irq);
 int intel_get_crtc_scanline(struct intel_crtc *crtc);
+int __intel_get_crtc_scanline(struct intel_crtc *crtc);
 void intel_wait_for_pipe_scanline_stopped(struct intel_crtc *crtc);
 void intel_wait_for_pipe_scanline_moving(struct intel_crtc *crtc);
 void intel_crtc_update_active_timings(const struct intel_crtc_state *crtc_state,
--- a/drivers/gpu/drm/i915/display/intel_crtc.c	2026-05-10 08:37:47.483199398 +0000
+++ b/drivers/gpu/drm/i915/display/intel_crtc.c	2026-05-10 08:37:47.486814189 +0000
@@ -604,7 +604,7 @@
 	struct intel_crtc_state *new_crtc_state =
 		intel_atomic_get_new_crtc_state(state, crtc);
 	enum pipe pipe = crtc->pipe;
-	int scanline_end = intel_get_crtc_scanline(crtc);
+	int scanline_end = __intel_get_crtc_scanline(crtc);
 	u32 end_vbl_count = intel_crtc_get_vblank_counter(crtc);
 	ktime_t end_vbl_time = ktime_get();
 	struct drm_i915_private *dev_priv = to_i915(crtc->base.dev);
--- a/drivers/gpu/drm/i915/display/intel_vblank.c	2026-05-10 08:38:03.920863967 +0000
+++ b/drivers/gpu/drm/i915/display/intel_vblank.c	2026-05-10 08:38:03.937934741 +0000
@@ -233,7 +233,7 @@
  * intel_de_read_fw(), only for fast reads of display block, no need for
  * forcewake etc.
  */
-static int __intel_get_crtc_scanline(struct intel_crtc *crtc)
+int __intel_get_crtc_scanline(struct intel_crtc *crtc)
 {
 	struct intel_display *display = to_intel_display(crtc);
 	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(&crtc->base);
@@ -663,24 +663,13 @@
 	struct intel_display *display = to_intel_display(crtc);
 	long timeout = msecs_to_jiffies_timeout(1);
 	wait_queue_head_t *wq = drm_crtc_vblank_waitqueue(&crtc->base);
-	DEFINE_WAIT(wait);
 	int scanline;
 
 	if (evade->min <= 0 || evade->max <= 0)
 		return 0;
 
-	for (;;) {
-		/*
-		 * prepare_to_wait() has a memory barrier, which guarantees
-		 * other CPUs can see the task state update by the time we
-		 * read the scanline.
-		 */
-		prepare_to_wait(wq, &wait, TASK_UNINTERRUPTIBLE);
-
-		scanline = intel_get_crtc_scanline(crtc);
-		if (scanline < evade->min || scanline > evade->max)
-			break;
-
+	scanline = __intel_get_crtc_scanline(crtc);
+	while (scanline >= evade->min && scanline <= evade->max) {
 		if (!timeout) {
 			drm_err(display->drm,
 				"Potential atomic update failure on pipe %c\n",
@@ -690,13 +679,15 @@
 
 		local_irq_enable();
 
-		timeout = schedule_timeout(timeout);
+		timeout = wait_event_timeout(*wq,
+			({ scanline = intel_get_crtc_scanline(crtc);
+			   scanline < evade->min || scanline > evade->max; }),
+			timeout);
 
 		local_irq_disable();
+		scanline = __intel_get_crtc_scanline(crtc);
 	}
 
-	finish_wait(wq, &wait);
-
 	/*
 	 * On VLV/CHV DSI the scanline counter would appear to
 	 * increment approx. 1/3 of a scanline before start of vblank.
--- cursor-only patch for intel_legacy_cursor_update ---

--- a/drivers/gpu/drm/i915/display/intel_cursor.c
+++ b/drivers/gpu/drm/i915/display/intel_cursor.c
@@ -800,5 +800,6 @@ intel_legacy_cursor_update(struct drm_plane *_plane,
 	struct intel_crtc_state *new_crtc_state;
 	struct intel_vblank_evade_ctx evade;
+	bool has_vblank = false;
 	int ret;
 
 	/*
@@ -896,18 +896,18 @@ intel_legacy_cursor_update(struct drm_plane *_plane,
 	intel_psr_lock(crtc_state);
 
 	if (!drm_WARN_ON(&i915->drm, drm_crtc_vblank_get(&crtc->base))) {
+		has_vblank = true;
+
 		/*
 		 * TODO: maybe check if we're still in PSR
 		 * and skip the vblank evasion entirely?
 		 */
 		intel_psr_wait_for_idle_locked(crtc_state);
 
 		local_irq_disable();
 
 		intel_vblank_evade(&evade);
-
-		drm_crtc_vblank_put(&crtc->base);
 	} else {
 		local_irq_disable();
 	}
 
@@ -921,8 +921,11 @@ intel_legacy_cursor_update(struct drm_plane *_plane,
 	local_irq_enable();
 
 	intel_psr_unlock(crtc_state);
 
+	if (has_vblank)
+		drm_crtc_vblank_put(&crtc->base);
+
 	if (old_plane_state->ggtt_vma != new_plane_state->ggtt_vma) {
 		drm_vblank_work_init(&old_plane_state->unpin_work, &crtc->base,
 				     intel_cursor_unpin_work);
 

Reply via email to