From 5c4063c87a619e4df954c179d24628636f5db15f Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Fri, 8 May 2026 14:23:51 +0200 Subject: [PATCH 1/5] drm/i915: Fix potential UAF in TTM object purge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TLDR: The bo->ttm object might be changed by calling ttm_bo_validate(), move casting it to an i915_tt object later to actually get the right pointer. A user reported hitting the following bug under heavy use on DG2: [26620.095550] Oops: general protection fault, probably for non-canonical address 0xa56b6b6b6b6b6b8b: 0000 1 SMP NOPTI [26620.095556] CPU: 2 UID: 0 PID: 631 Comm: Xorg Not tainted 6.18.8 #1 PREEMPT(lazy) [26620.095558] Hardware name: ASRock B850M Steel Legend WiFi/B850M Steel Legend WiFi, BIOS 3.50 09/18/2025 [26620.095559] RIP: 0010:i915_ttm_purge+0x84/0x100 [i915] [26620.095604] Code: 00 00 00 48 8d 54 24 10 48 89 e6 48 89 fb e8 83 aa ae ff 85 c0 75 6f 48 83 bb a8 01 00 00 00 74 2c 48 8b 45 78 48 85 c0 74 23 <48> 8b 78 20 48 c7 c2 ff ff ff ff 31 f6 e8 7a 73 e3 e0 48 8b 7d 78 [26620.095605] RSP: 0018:ffffc90005fd7430 EFLAGS: 00010282 [26620.095607] RAX: a56b6b6b6b6b6b6b RBX: ffff8881f46c3dc0 RCX: 0000000000000000 [26620.095608] RDX: 0000000000000000 RSI: 0000000000000246 RDI: 00000000ffffffff [26620.095609] RBP: ffff888289610f00 R08: 0000000000000001 R09: ffff88823b022000 [26620.095609] R10: ffff888103029b28 R11: ffff8881fc7f3800 R12: ffff88810b6150d0 [26620.095609] R13: ffff888289610f00 R14: 0000000000000000 R15: ffff8881f46c3dc0 [26620.095610] FS: 00007f1004d86900(0000) GS:ffff88901c858000(0000) knlGS:0000000000000000 [26620.095611] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [26620.095611] CR2: 00007f0fdf489000 CR3: 000000035b0c1000 CR4: 0000000000750ef0 [26620.095612] PKRU: 55555554 [26620.095612] Call Trace: [26620.095615] [26620.095615] i915_ttm_move+0x2b9/0x420 [i915] [26620.095642] ? ttm_tt_init+0x65/0x80 [ttm] [26620.095644] ? i915_ttm_tt_create+0xc6/0x150 [i915] [26620.095667] ttm_bo_handle_move_mem+0xb6/0x160 [ttm] [26620.095669] ttm_bo_evict+0x100/0x150 [ttm] [26620.095671] ? preempt_count_add+0x64/0xa0 [26620.095673] ? _raw_spin_lock+0xe/0x30 [26620.095675] ? _raw_spin_unlock+0xd/0x30 [26620.095675] ? i915_gem_object_evictable+0xb7/0xd0 [i915] [26620.095704] ttm_bo_evict_cb+0x6e/0xd0 [ttm] [26620.095705] ttm_lru_walk_for_evict+0xa6/0x200 [ttm] [26620.095708] ttm_bo_alloc_resource+0x185/0x4f0 [ttm] [26620.095709] ? init_object+0x62/0xd0 [26620.095712] ttm_bo_validate+0x7a/0x180 [ttm] [26620.095713] ? _raw_spin_unlock_irqrestore+0x16/0x30 [26620.095714] __i915_ttm_get_pages+0xb0/0x170 [i915] [26620.095737] i915_ttm_get_pages+0x9f/0x150 [i915] [26620.095759] ? i915_gem_do_execbuffer+0xedc/0x2b40 [i915] [26620.095786] ? alloc_debug_processing+0xd0/0x100 [26620.095787] ? _raw_spin_unlock_irqrestore+0x16/0x30 [26620.095788] ? i915_vma_instance+0xa0/0x4e0 [i915] [26620.095822] __i915_gem_object_get_pages+0x2f/0x40 [i915] [26620.095848] i915_vma_pin_ww+0x706/0x980 [i915] [26620.095875] ? i915_gem_do_execbuffer+0xedc/0x2b40 [i915] [26620.095904] eb_validate_vmas+0x170/0xa00 [i915] [26620.095930] i915_gem_do_execbuffer+0x1201/0x2b40 [i915] [26620.095953] ? alloc_debug_processing+0xd0/0x100 [26620.095954] ? _raw_spin_unlock_irqrestore+0x16/0x30 [26620.095955] ? i915_gem_execbuffer2_ioctl+0xc9/0x240 [i915] [26620.095977] ? __wake_up_sync_key+0x32/0x50 [26620.095979] ? i915_gem_execbuffer2_ioctl+0xc9/0x240 [i915] [26620.096001] ? __slab_alloc.isra.0+0x67/0xc0 [26620.096003] i915_gem_execbuffer2_ioctl+0x11a/0x240 [i915] Results from decode_stacktrace.sh pointed to dereference of a file pointer field of a i915 TTM page vector container associated with an object being purged on eviction. That path is taken when the object is marked as no longer needed. Code analysis revealed a possibility of the i915 TTM page vector container being replaced with a new instance inside a function that purges content of the object, should it be still busy. That function is called, indirectly via a more general function that changes the object's placement and caching policy, before the problematic dereference, but still after a pointer to the container is captured, rendering the pointer no longer valid. Fix the issue by capturing the pointer to the container only after its potential replacement. v2: Move the container_of() inside the if block (Sebastian), - a simplified version of the commit description that explains briefly why the change is necessary (Christian). Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/14882 Fixes: 7ae034590ceae ("drm/i915/ttm: add tt shmem backend") Signed-off-by: Janusz Krzysztofik Cc: stable@vger.kernel.org # v5.17+ Cc: Matthew Auld Cc: Thomas Hellström Cc: Sebastian Brzezinka Cc: Christian König Reviewed-by: Andi Shyti Reviewed-by: Christian König Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260508122612.469227-2-janusz.krzysztofik@linux.intel.com (cherry picked from commit 4462966a93eb185849b7f174f0d0de53476d00a4) Signed-off-by: Tvrtko Ursulin --- drivers/gpu/drm/i915/gem/i915_gem_ttm.c | 28 ++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c index de70517b4ef2..df3fcc2b1248 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c @@ -419,8 +419,6 @@ void i915_ttm_free_cached_io_rsgt(struct drm_i915_gem_object *obj) int i915_ttm_purge(struct drm_i915_gem_object *obj) { struct ttm_buffer_object *bo = i915_gem_to_ttm(obj); - struct i915_ttm_tt *i915_tt = - container_of(bo->ttm, typeof(*i915_tt), ttm); struct ttm_operation_ctx ctx = { .interruptible = true, .no_wait_gpu = false, @@ -435,16 +433,22 @@ int i915_ttm_purge(struct drm_i915_gem_object *obj) if (ret) return ret; - if (bo->ttm && i915_tt->filp) { - /* - * The below fput(which eventually calls shmem_truncate) might - * be delayed by worker, so when directly called to purge the - * pages(like by the shrinker) we should try to be more - * aggressive and release the pages immediately. - */ - shmem_truncate_range(file_inode(i915_tt->filp), - 0, (loff_t)-1); - fput(fetch_and_zero(&i915_tt->filp)); + if (bo->ttm) { + struct i915_ttm_tt *i915_tt = + container_of(bo->ttm, typeof(*i915_tt), ttm); + + if (i915_tt->filp) { + /* + * The below fput(which eventually calls shmem_truncate) + * might be delayed by worker, so when directly called + * to purge the pages(like by the shrinker) we should + * try to be more aggressive and release the pages + * immediately. + */ + shmem_truncate_range(file_inode(i915_tt->filp), + 0, (loff_t)-1); + fput(fetch_and_zero(&i915_tt->filp)); + } } obj->write_domain = 0; From 202e77cf2e839e1adc804433322dc5c9ee511c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Grzelak?= Date: Thu, 16 Apr 2026 18:37:44 +0200 Subject: [PATCH 2/5] drm/i915/aux: use polling when irqs are unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PTL with physically disconnected display was observed to have 40s longer execution time when testing xe_fault_injection@xe_guc_mmio_send_recv. The issue has not been seen when reverting commit 40a9f77a28fa ("Revert "drm/i915/dp: change aux_ctl reg read to polling read""). Apparently the configuration suffers from not having AUX enabled when using interrupts. One probable cause can be xe enabling interrupts too late: interrupts need memory allocations which currently can't be done before the display FB takeover is done. As for now, use polling for AUX in case interrupts are unavailable. Fixes: 40a9f77a28fa ("Revert "drm/i915/dp: change aux_ctl reg read to polling read"") Suggested-by: Ville Syrjälä Signed-off-by: Michał Grzelak Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260416163744.288107-1-michal.grzelak@intel.com (cherry picked from commit 05e0550b65cd1604bd515fbc65f522bce4c10a87) Signed-off-by: Tvrtko Ursulin --- drivers/gpu/drm/i915/display/intel_dp_aux.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_aux.c b/drivers/gpu/drm/i915/display/intel_dp_aux.c index b20ec3e589fa..9c9b6410366d 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_aux.c +++ b/drivers/gpu/drm/i915/display/intel_dp_aux.c @@ -12,6 +12,7 @@ #include "intel_dp.h" #include "intel_dp_aux.h" #include "intel_dp_aux_regs.h" +#include "intel_parent.h" #include "intel_pps.h" #include "intel_quirks.h" #include "intel_tc.h" @@ -60,18 +61,29 @@ intel_dp_aux_wait_done(struct intel_dp *intel_dp) struct intel_display *display = to_intel_display(intel_dp); i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg(intel_dp); const unsigned int timeout_ms = 10; + bool done = true; u32 status; - bool done; + int ret; + if (intel_parent_irq_enabled(display)) { #define C (((status = intel_de_read_notrace(display, ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) - done = wait_event_timeout(display->gmbus.wait_queue, C, - msecs_to_jiffies_timeout(timeout_ms)); + done = wait_event_timeout(display->gmbus.wait_queue, C, + msecs_to_jiffies_timeout(timeout_ms)); + +#undef C + } else { + ret = intel_de_wait_ms(display, ch_ctl, + DP_AUX_CH_CTL_SEND_BUSY, 0, + timeout_ms, &status); + + if (ret == -ETIMEDOUT) + done = false; + } if (!done) drm_err(display->drm, "%s: did not complete or timeout within %ums (status 0x%08x)\n", intel_dp->aux.name, timeout_ms, status); -#undef C return status; } From d196136a988051173f68f91de0b5a1bd32122dd7 Mon Sep 17 00:00:00 2001 From: Pranay Samala Date: Tue, 19 May 2026 13:23:08 +0530 Subject: [PATCH 3/5] drm/i915/color: Fix HDR pre-CSC LUT programming loop The integer lut programming loop never executes completely due to incorrect condition (i++ > 130). Fix to properly program 129th+ entries for values > 1.0. Cc: #v6.19 Fixes: 82caa1c8813f ("drm/i915/color: Program Pre-CSC registers") Signed-off-by: Pranay Samala Signed-off-by: Chaitanya Kumar Borah Reviewed-by: Uma Shankar Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260519075308.383877-1-pranay.samala@intel.com (cherry picked from commit f33862ec3e8849ad7c0a3dd46719083b13ade248) Signed-off-by: Tvrtko Ursulin --- drivers/gpu/drm/i915/display/intel_color.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_color.c b/drivers/gpu/drm/i915/display/intel_color.c index e7950655434b..6d1cffc6d2be 100644 --- a/drivers/gpu/drm/i915/display/intel_color.c +++ b/drivers/gpu/drm/i915/display/intel_color.c @@ -3976,7 +3976,7 @@ xelpd_program_plane_pre_csc_lut(struct intel_dsb *dsb, intel_de_write_dsb(display, dsb, PLANE_PRE_CSC_GAMC_DATA_ENH(pipe, plane, 0), (1 << 24)); - } while (i++ > 130); + } while (i++ < 130); } else { for (i = 0; i < lut_size; i++) { u32 v = (i * ((1 << 24) - 1)) / (lut_size - 1); From 8bb9093df555f9e89fdbe1405118b11384c03e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20H=C3=B6gander?= Date: Wed, 20 May 2026 13:49:43 +0300 Subject: [PATCH 4/5] drm/i915/psr: Block DC states on vblank enable when Panel Replay supported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we are blocking DC states only when Panel Replay is enabled on vblank enable. It may happen that Panel Replay is getting enabled when vblank is already enabled. Fix this by blocking DC states always if Panel Replay is supported. While at it take care of possible dual eDP case by looping all encoders supporting PSR. Fixes: 0c427ac78a1d ("drm/i915/psr: Add interface to notify PSR of vblank enable/disable") Cc: # v6.16+ Signed-off-by: Jouni Högander Reviewed-by: Michał Grzelak Link: https://patch.msgid.link/20260520104944.239797-1-jouni.hogander@intel.com (cherry picked from commit eb5911f990554f7ce947dd53df00c114362e4465) Signed-off-by: Tvrtko Ursulin --- drivers/gpu/drm/i915/display/intel_psr.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c index 29904a037575..bd5a8c6ac6ef 100644 --- a/drivers/gpu/drm/i915/display/intel_psr.c +++ b/drivers/gpu/drm/i915/display/intel_psr.c @@ -4151,32 +4151,33 @@ void intel_psr_notify_vblank_enable_disable(struct intel_display *display, bool enable) { struct intel_encoder *encoder; + bool block_dc_states = false; for_each_intel_encoder_with_psr(display->drm, encoder) { struct intel_dp *intel_dp = enc_to_intel_dp(encoder); mutex_lock(&intel_dp->psr.lock); - if (intel_dp->psr.panel_replay_enabled) { - mutex_unlock(&intel_dp->psr.lock); - break; - } + if (CAN_PANEL_REPLAY(intel_dp)) + block_dc_states = true; - if (intel_dp->psr.enabled && intel_dp->psr.pkg_c_latency_used) + if (intel_dp->psr.enabled && !intel_dp->psr.panel_replay_enabled && + intel_dp->psr.pkg_c_latency_used) intel_psr_apply_underrun_on_idle_wa_locked(intel_dp); mutex_unlock(&intel_dp->psr.lock); - return; } /* * NOTE: intel_display_power_set_target_dc_state is used - * only by PSR * code for DC3CO handling. DC3CO target + * only by PSR code for DC3CO handling. DC3CO target * state is currently disabled in * PSR code. If DC3CO * is taken into use we need take that into account here * as well. */ - intel_display_power_set_target_dc_state(display, enable ? DC_STATE_DISABLE : - DC_STATE_EN_UPTO_DC6); + if (block_dc_states) + intel_display_power_set_target_dc_state(display, enable ? + DC_STATE_DISABLE : + DC_STATE_EN_UPTO_DC6); } static void From 3549a9649dc7c5fc586ab12f675279283cdcb2a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20H=C3=B6gander?= Date: Wed, 20 May 2026 13:49:44 +0300 Subject: [PATCH 5/5] drm/i915/psr: Use DC_OFF wake reference to block DC6 on vblank enable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We are observing following warnings: *ERROR* power well DC_off state mismatch (refcount 0/enabled 1) gen9_dc_off_power_well_enabled is considering target state DC_STATE_DISABLE as DC_OFF power well being enabled. Fix this by using wakeref for the purpose. To achieve this we need to modify notification code as well. Currently it is possible that PSR gets notified vblank enable/disable twice on same status. This is currently not a problem as it is just triggering call to intel_display_power_set_target_dc_state with same target state as a parameter. When using wakeref this becomes a problem due to reference counting. Fix this storing vbank status on last notification and use that to ensure there are no more than one notification with same vblank status. v2: ensure there is no subsequent notifications with same status Fixes: aa451abcffb5 ("drm/i915/display: Prevent DC6 while vblank is enabled for Panel Replay") Cc: # v6.13+ Signed-off-by: Jouni Högander Reviewed-by: Michał Grzelak Link: https://patch.msgid.link/20260520104944.239797-2-jouni.hogander@intel.com (cherry picked from commit 35485ac56d878192a3829a58cb26503125ec7104) Signed-off-by: Tvrtko Ursulin --- .../gpu/drm/i915/display/intel_display_core.h | 1 + .../gpu/drm/i915/display/intel_display_irq.c | 8 +++++-- .../drm/i915/display/intel_display_types.h | 2 ++ drivers/gpu/drm/i915/display/intel_psr.c | 24 +++++++------------ 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_display_core.h b/drivers/gpu/drm/i915/display/intel_display_core.h index d9baca2d5aaf..78afcd42f44c 100644 --- a/drivers/gpu/drm/i915/display/intel_display_core.h +++ b/drivers/gpu/drm/i915/display/intel_display_core.h @@ -497,6 +497,7 @@ struct intel_display { u8 vblank_enabled; int vblank_enable_count; + bool vblank_status_last_notified; struct work_struct vblank_notify_work; diff --git a/drivers/gpu/drm/i915/display/intel_display_irq.c b/drivers/gpu/drm/i915/display/intel_display_irq.c index 70c1bba7c0a8..aedf3928a089 100644 --- a/drivers/gpu/drm/i915/display/intel_display_irq.c +++ b/drivers/gpu/drm/i915/display/intel_display_irq.c @@ -1773,8 +1773,12 @@ static void intel_display_vblank_notify_work(struct work_struct *work) struct intel_display *display = container_of(work, typeof(*display), irq.vblank_notify_work); int vblank_enable_count = READ_ONCE(display->irq.vblank_enable_count); + bool vblank_status = !!vblank_enable_count; - intel_psr_notify_vblank_enable_disable(display, vblank_enable_count); + if (display->irq.vblank_status_last_notified != vblank_status) { + intel_psr_notify_vblank_enable_disable(display, vblank_status); + display->irq.vblank_status_last_notified = vblank_status; + } } int bdw_enable_vblank(struct drm_crtc *_crtc) @@ -1787,10 +1791,10 @@ int bdw_enable_vblank(struct drm_crtc *_crtc) if (gen11_dsi_configure_te(crtc, true)) return 0; + spin_lock_irqsave(&display->irq.lock, irqflags); if (crtc->vblank_psr_notify && display->irq.vblank_enable_count++ == 0) schedule_work(&display->irq.vblank_notify_work); - spin_lock_irqsave(&display->irq.lock, irqflags); bdw_enable_pipe_irq(display, pipe, GEN8_PIPE_VBLANK); spin_unlock_irqrestore(&display->irq.lock, irqflags); diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index 9c7c357afb09..2e6a85708555 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -1790,6 +1790,8 @@ struct intel_psr { u8 active_non_psr_pipes; const char *no_psr_reason; + + struct ref_tracker *vblank_wakeref; }; struct intel_dp { diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c index bd5a8c6ac6ef..598fe769a402 100644 --- a/drivers/gpu/drm/i915/display/intel_psr.c +++ b/drivers/gpu/drm/i915/display/intel_psr.c @@ -4151,14 +4151,20 @@ void intel_psr_notify_vblank_enable_disable(struct intel_display *display, bool enable) { struct intel_encoder *encoder; - bool block_dc_states = false; for_each_intel_encoder_with_psr(display->drm, encoder) { struct intel_dp *intel_dp = enc_to_intel_dp(encoder); mutex_lock(&intel_dp->psr.lock); - if (CAN_PANEL_REPLAY(intel_dp)) - block_dc_states = true; + if (CAN_PANEL_REPLAY(intel_dp)) { + if (enable) + intel_dp->psr.vblank_wakeref = + intel_display_power_get(display, + POWER_DOMAIN_DC_OFF); + else + intel_display_power_put(display, POWER_DOMAIN_DC_OFF, + intel_dp->psr.vblank_wakeref); + } if (intel_dp->psr.enabled && !intel_dp->psr.panel_replay_enabled && intel_dp->psr.pkg_c_latency_used) @@ -4166,18 +4172,6 @@ void intel_psr_notify_vblank_enable_disable(struct intel_display *display, mutex_unlock(&intel_dp->psr.lock); } - - /* - * NOTE: intel_display_power_set_target_dc_state is used - * only by PSR code for DC3CO handling. DC3CO target - * state is currently disabled in * PSR code. If DC3CO - * is taken into use we need take that into account here - * as well. - */ - if (block_dc_states) - intel_display_power_set_target_dc_state(display, enable ? - DC_STATE_DISABLE : - DC_STATE_EN_UPTO_DC6); } static void