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);