From 94cf5971b1d04c6382437387c4dc3836cc7a1437 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 15 Sep 2026 09:51:03 +0000 Subject: drm/vc4: drain the hangcheck timer and works on V3D unbind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing stops the hangcheck timer that submitted jobs arm, or the job_done_work that the render-done interrupt queues, at teardown: vc4_irq_disable() cancels only overflow_mem_work, and vc4_gem_destroy() runs from the drm-managed release, after vc4_v3d_unbind() has already uninstalled the V3D interrupt and cleared vc4->v3d. A timer still armed by then reads V3D registers through the NULL vc4->v3d pointer, and the late callbacks run on the vc4_dev embedding them after it has been freed. Drain them in vc4_v3d_unbind(): shut the timer down and cancel reset_work before the interrupt is taken down, because its vc4_irq_reset() re-enables it, then flush job_done_work so that completions queued at teardown still release their jobs. This issue was found by an in-house static analysis tool. Fixes: d5b1a78a772f ("drm/vc4: Add support for drawing 3D frames.") Cc: stable@vger.kernel.org # 6.2+ Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260915095103.156007-1-fanwu01@zju.edu.cn Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_v3d.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index f32410420d3e..0f0c090afa6e 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -497,8 +498,15 @@ static void vc4_v3d_unbind(struct device *dev, struct device *master, struct drm_device *drm = data; struct vc4_dev *vc4 = to_vc4_dev(drm); + /* A straggler vc4_reset() re-enables the interrupt. */ + timer_shutdown_sync(&vc4->hangcheck.timer); + cancel_work_sync(&vc4->hangcheck.reset_work); + vc4_irq_uninstall(drm); + /* Flush rather than cancel, so queued completions release their jobs. */ + flush_work(&vc4->job_done_work); + /* Disable the binner's overflow memory address, so the next * driver probe (if any) doesn't try to reuse our old * allocation. -- cgit v1.2.3 From b816da5d5df1461446a0a5f3fe2b4948a659a790 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 15:40:15 +0000 Subject: drm/vc4: Fix firmware reference leak in vc4_drm_bind() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The firmware reference obtained via rpi_firmware_get() is leaked when aperture_remove_all_conflicting_devices() fails, as the error label returns without putting it. This happens before the firmware is consumed and put further down in the function. Put the firmware reference before jumping to err. Fixes: c406ad5e4a85 ("drm/vc4: Notify the firmware when DRM is in charge") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260916154015.2056797-1-vulab@iscas.ac.cn Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_drv.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 616caf9d9915..7b64e1ed02d4 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -360,8 +360,11 @@ static int vc4_drm_bind(struct device *dev) } ret = aperture_remove_all_conflicting_devices(driver->name); - if (ret) + if (ret) { + if (firmware) + rpi_firmware_put(firmware); goto err; + } if (firmware) { ret = rpi_firmware_property(firmware, -- cgit v1.2.3 From add916cfcec5b71cb242db85b91b2437be7390cf Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 21 Mar 2026 03:12:10 +0800 Subject: drm/bridge: th1520-dw-hdmi: Fix error check on dw_hdmi_probe() return value The error check after calling dw_hdmi_probe() was incorrectly checking the struct pointer hdmi instead of the probe result hdmi->dw_hdmi. Fix this by checking the correct variable. Fixes: 96f30ee0fb9d ("drm/bridge: add a driver for T-Head TH1520 HDMI controller") Signed-off-by: Felix Gu Reviewed-by: Dmitry Baryshkov Cc: stable@vger.kernel.org Signed-off-by: Icenowy Zheng Link: https://patch.msgid.link/20260321-th1520-v1-1-ec877197770d@gmail.com --- drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c index cbea8b14cd4b..3464720b4e13 100644 --- a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c @@ -135,8 +135,8 @@ static int th1520_dw_hdmi_probe(struct platform_device *pdev) plat_data->priv_data = hdmi; hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); - if (IS_ERR(hdmi)) - return PTR_ERR(hdmi); + if (IS_ERR(hdmi->dw_hdmi)) + return PTR_ERR(hdmi->dw_hdmi); platform_set_drvdata(pdev, hdmi); -- cgit v1.2.3 From 01c83795d50ccdb441f60e89ff50767cb2f6045a Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 21 Mar 2026 03:12:11 +0800 Subject: drm/bridge: th1520-dw-hdmi: Fix remove() callback This driver stores struct th1520_hdmi * in platform drvdata, but th1520_dw_hdmi_remove() was reading it back as struct dw_hdmi * and passing it to dw_hdmi_remove(), so teardown runs on the wrong pointer. Retrieve struct th1520_hdmi * from platform drvdata and pass hdmi->dw_hdmi to dw_hdmi_remove(). Fixes: 96f30ee0fb9d ("drm/bridge: add a driver for T-Head TH1520 HDMI controller") Signed-off-by: Felix Gu Reviewed-by: Dmitry Baryshkov Cc: stable@vger.kernel.org Signed-off-by: Icenowy Zheng Link: https://patch.msgid.link/20260321-th1520-v1-2-ec877197770d@gmail.com --- drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c index 3464720b4e13..2fa7f2d46b3d 100644 --- a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c @@ -145,9 +145,9 @@ static int th1520_dw_hdmi_probe(struct platform_device *pdev) static void th1520_dw_hdmi_remove(struct platform_device *pdev) { - struct dw_hdmi *hdmi = platform_get_drvdata(pdev); + struct th1520_hdmi *hdmi = platform_get_drvdata(pdev); - dw_hdmi_remove(hdmi); + dw_hdmi_remove(hdmi->dw_hdmi); } static const struct of_device_id th1520_dw_hdmi_of_table[] = { -- cgit v1.2.3 From 21bc51cb89ced74d6645a138cc2f21184bc75094 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Sun, 9 Aug 2026 16:45:15 -0400 Subject: drm/vmwgfx: validate pitch coming from userspace Validate the pitch, alongside the box dimensions before trying to copy data from the underlying surface. Fixes possible out of bounds reads with cursor snooping. Fixes: 2ac863719e51 ("vmwgfx: Snoop DMA transfers with non-covering sizes") Cc: stable@vger.kernel.org Reported-by: Youness HFA Signed-off-by: Zack Rusin Reviewed-by: Maaz Mombasawala Link: https://patch.msgid.link/20260809204515.211045-1-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c b/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c index d1e7df500190..5a69606a0593 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c @@ -324,6 +324,7 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, unsigned long kmap_num; SVGA3dCopyBox *box; u32 box_count; + u64 src_extent; void *virtual; bool is_iomem; struct vmw_dma_cmd { @@ -335,6 +336,8 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, vmw_surface_get_desc(VMW_CURSOR_SNOOP_FORMAT); const u32 image_pitch = VMW_CURSOR_SNOOP_WIDTH * desc->pitchBytesPerBlock; + BUILD_BUG_ON(sizeof(*srf->snooper.image) != desc->pitchBytesPerBlock); + cmd = container_of(header, struct vmw_dma_cmd, header); /* No snooper installed, nothing to copy */ @@ -372,8 +375,19 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, return; } + if (box->w == 0 || box->h == 0) + return; + + src_extent = (u64)(box->h - 1) * cmd->dma.guest.pitch + + (u64)box->w * desc->pitchBytesPerBlock; + if (src_extent > bo->base.size) { + DRM_ERROR("Cursor snoop source of %llu bytes exceeds the %zu byte buffer\n", + src_extent, bo->base.size); + return; + } + kmap_offset = cmd->dma.guest.ptr.offset >> PAGE_SHIFT; - kmap_num = (VMW_CURSOR_SNOOP_HEIGHT * image_pitch) >> PAGE_SHIFT; + kmap_num = PFN_UP(src_extent); ret = ttm_bo_reserve(bo, true, false, NULL); if (unlikely(ret != 0)) { @@ -387,14 +401,16 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, virtual = ttm_kmap_obj_virtual(&map, &is_iomem); - if (box->w == VMW_CURSOR_SNOOP_WIDTH && cmd->dma.guest.pitch == image_pitch) { + if (box->w == VMW_CURSOR_SNOOP_WIDTH && + box->h == VMW_CURSOR_SNOOP_HEIGHT && + cmd->dma.guest.pitch == image_pitch) { memcpy(srf->snooper.image, virtual, VMW_CURSOR_SNOOP_HEIGHT * image_pitch); } else { - /* Image is unsigned pointer. */ + /* The destination stride is in pixels. */ for (i = 0; i < box->h; i++) - memcpy(srf->snooper.image + i * image_pitch, - virtual + i * cmd->dma.guest.pitch, + memcpy(srf->snooper.image + i * VMW_CURSOR_SNOOP_WIDTH, + virtual + (size_t)i * cmd->dma.guest.pitch, box->w * desc->pitchBytesPerBlock); } srf->snooper.id++; -- cgit v1.2.3 From e78a9fb7a40c55ec2a70ae39903dde4f898af2ef Mon Sep 17 00:00:00 2001 From: Ian Forbes Date: Wed, 16 Sep 2026 14:01:44 -0500 Subject: drm/vmwgfx: Add blend mode property This stops the log spamming introduced by the referenced fixes commit. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Signed-off-by: Ian Forbes Reviewed-by: Zack Rusin Signed-off-by: Zack Rusin Link: https://patch.msgid.link/20260916190144.2080777-1-ian.forbes@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_kms.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c index 1b407b61f683..31d3ee0825ca 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c @@ -20,10 +20,18 @@ #include #include #include +#include void vmw_du_init(struct vmw_display_unit *du) { vmw_vkms_crtc_init(&du->crtc); + drm_plane_create_blend_mode_property(&du->primary, + BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI)); + // Check that the cursor plane was actually initialized + if (du->crtc.cursor == &du->cursor.base) + drm_plane_create_blend_mode_property(&du->cursor.base, + BIT(DRM_MODE_BLEND_PREMULTI)); } void vmw_du_cleanup(struct vmw_display_unit *du) -- cgit v1.2.3 From b62e3db8cdc1680e1c3390591e872f485148541a Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:36 +0300 Subject: drm/bridge: Fix unlocked list_del in drm_bridge_add() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When re-adding a bridge that was previously removed, drm_bridge_add() drops it from bridge_lingering_list without holding bridge_lock. Both bridge_list and bridge_lingering_list are protected by bridge_lock, as they are concurrently modified by drm_bridge_remove() and __drm_bridge_free(), and walked by the debugfs 'bridges' file. Running the list_empty() test and the list_del_init() outside of the lock may therefore corrupt either list. Perform both under bridge_lock. Fixes: 17805a15d175 ("drm/bridge: add list of removed refcounted bridges") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260723015004.1F5711F000E9@smtp.kernel.org/ Signed-off-by: Cristian Ciocaltea Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-12-bc12954a0688@collabora.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 83f1809a5d37..04b999305f30 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -454,8 +454,10 @@ void drm_bridge_add(struct drm_bridge *bridge) * in bridge_lingering_list. Remove it or bridge_lingering_list will be * corrupted when adding this bridge to bridge_list below. */ + mutex_lock(&bridge_lock); if (!list_empty(&bridge->list)) list_del_init(&bridge->list); + mutex_unlock(&bridge_lock); mutex_init(&bridge->hpd_state_mutex); mutex_init(&bridge->hpd_mutex); -- cgit v1.2.3 From 57889076afd1be890150c32ffde3b691fe5707c8 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:38 +0300 Subject: drm/bridge: Fix unlocked list access in drm_bridge_attach() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_bridge_attach() inspects bridge->list to warn about bridges being attached without a previous drm_bridge_add() call, but it does so without holding bridge_lock. The list head can be concurrently written by drm_bridge_add(), drm_bridge_remove() and __drm_bridge_free() while they operate on neighbouring entries of bridge_list or bridge_lingering_list, all of them under bridge_lock. Hence the unlocked read is a data race, even though it is unlikely to change the outcome of list_empty() in practice. Perform the check with bridge_lock held. Fixes: 76f1a9711b83 ("drm/bridge: add warning for bridges attached without being added") Signed-off-by: Cristian Ciocaltea Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-14-bc12954a0688@collabora.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 04b999305f30..a52d5f5b9f05 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -595,8 +595,10 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge, if (!bridge->container) DRM_WARN("DRM bridge corrupted or not allocated by devm_drm_bridge_alloc()\n"); - if (list_empty(&bridge->list)) - DRM_WARN("Missing drm_bridge_add() before attach\n"); + scoped_guard(mutex, &bridge_lock) { + if (list_empty(&bridge->list)) + DRM_WARN("Missing drm_bridge_add() before attach\n"); + } drm_bridge_get(bridge); -- cgit v1.2.3 From ec86c43dcfff68eab4db9bdafcd3e2b16e342e18 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Tue, 4 Aug 2026 18:00:47 +0200 Subject: drm/panthor: validate userspace queue count against initialized firmware slot count In panthor_drv.c:panthor_ioctl_group_create(), the queue count is validated against the hardcoded maximum: if (!args->queues.count || args->queues.count > MAX_CS_PER_CSG) return -EINVAL; However, if the firmware reports fewer slots than MAX_CS_PER_CSG, panthor_fw_init_ifaces() will only initialize that smaller number of stream interfaces. If unprivileged userspace requests more queues than the firmware reported (but no more than MAX_CS_PER_CSG), cs_slot_prog_locked() fetches an uninitialized stream interface and may crash when dereferencing cs_iface->input->ringbuf_base. Fixes: de8548813824 ("drm/panthor: Add the scheduler logical block") Cc: stable@vger.kernel.org Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260803141149.68182-1-osama.abdelkader@gmail.com?part=1 Signed-off-by: Osama Abdelkader Reviewed-by: Steven Price Reviewed-by: Liviu Dudau Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20260804160048.174148-1-osama.abdelkader@gmail.com Signed-off-by: Steven Price --- drivers/gpu/drm/panthor/panthor_sched.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 5778e23e20a0..653c6755896c 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -3674,6 +3674,9 @@ int panthor_group_create(struct panthor_file *pfile, hweight64(group_args->tiler_core_mask) < group_args->max_tiler_cores) return -EINVAL; + if (group_args->queues.count > sched->cs_slot_count) + return -EINVAL; + group = kzalloc_obj(*group); if (!group) return -ENOMEM; -- cgit v1.2.3 From 707ce102521496c67860d81221b20c56d8255f2f Mon Sep 17 00:00:00 2001 From: Oleg Keri Date: Fri, 25 Sep 2026 15:25:58 +0200 Subject: drm/dp: don't mark the AUX backlight enabled when enabling failed If drm_edp_backlight_enable() fails, dp_aux_backlight_update_status() still marks the backlight as enabled. From then on every brightness change only updates the level and the enable is never retried, so the panel stays dark until something blanks and unblanks the backlight. Return the error instead, so the next update tries to enable again. Fixes: 10f7b40e4f30 ("drm/panel: add basic DP AUX backlight support") Signed-off-by: Oleg Keri Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260925132558.27518-1-okerixx@gmail.com --- drivers/gpu/drm/display/drm_dp_helper.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c index e623ccb4c1d8..488fd123a01a 100644 --- a/drivers/gpu/drm/display/drm_dp_helper.c +++ b/drivers/gpu/drm/display/drm_dp_helper.c @@ -4634,7 +4634,9 @@ static int dp_aux_backlight_update_status(struct backlight_device *bd) if (!backlight_is_blank(bd)) { if (!bl->enabled) { - drm_edp_backlight_enable(bl->aux, &bl->info, brightness); + ret = drm_edp_backlight_enable(bl->aux, &bl->info, brightness); + if (ret) + return ret; bl->enabled = true; return 0; } -- cgit v1.2.3 From 4d0e2704118511e37790641d591454354e7c0d6c Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Wed, 30 Sep 2026 17:36:15 +1000 Subject: nouveau: check gsp->rm as well as gsp pointer before gcx ready In turing systems where gsp exists, but can end up not being used, we need to add a second check. Fixes: cb4c7603678c ("drm/nouveau/gsp/r570: Add support for INTERNAL_GCX_ENTRY_PREREQUISITE") Reported-by: Woody Suwalski Tested-by: Woody Suwalski Reviewed-by: Lyude Paul Signed-off-by: Dave Airlie Link: https://lore.kernel.org/nouveau/20260930212151.3926202-1-airlied@gmail.com/T/#u --- drivers/gpu/drm/nouveau/nvkm/engine/device/user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c index f78e6b9b4292..b841d66f8bb3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c @@ -201,7 +201,7 @@ nvkm_udevice_gcx_ready(struct nvkm_udevice *udev, void *data, u32 size) } *args = data; int ret = -ENOSYS; - if (!gsp) { + if (!gsp || !gsp->rm) { args->v0.ready = NV_DEVICE_GC6_READY | NV_DEVICE_GCOFF_READY; return 0; } -- cgit v1.2.3 From 755c94d178b7e972bb766199d34e4ba4f63a89eb Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sun, 27 Sep 2026 14:54:34 +0700 Subject: drm/fb-helper: defer setup when fbdev_probe() fails, not just on -EAGAIN __drm_fb_helper_initial_config_and_unlock() calls drm_client_modeset_probe() first, which already fills in each mode_set->mode/num_connectors for every connected output, and only afterwards calls drm_fb_helper_single_fb_probe() to create fb_helper->fb and wire it into those mode_sets via drm_setup_crtcs_fb(). If drm_fb_helper_single_fb_probe() fails with anything other than -EAGAIN (e.g. dev->driver->fbdev_probe() itself fails), the function bails out before drm_setup_crtcs_fb() runs and before setting fb_helper->deferred_setup. The client is left with mode_sets that have a mode and connectors but mode_set->fb == NULL, and nothing marks this fb_helper as needing a retry. __drm_fb_helper_restore_fbdev_mode_unlocked() only skips committing when fb_helper->deferred_setup is set, so a later .restore() call (drm_lastclose() on process exit, in this report) commits the stale mode_set through drm_client_modeset_commit() -> drm_client_modeset_commit_atomic() -> __drm_atomic_helper_set_config(), which hits WARN_ON(!set->fb). Mark fb_helper->deferred_setup on any drm_fb_helper_single_fb_probe() failure, not just -EAGAIN, so restore keeps skipping the commit until a later hotplug event successfully re-probes and re-populates mode_set->fb. The real error code is still returned to the caller unchanged. Reported-by: syzbot+ca23c8570669ead78867@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ca23c8570669ead78867 Fixes: ca91a2758fce ("drm/fb-helper: Support deferred setup") Cc: stable@vger.kernel.org Signed-off-by: Nguyen Ngoc Thang Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260927075434.72658-1-ngocthang2710.1999@gmail.com Link: https://patch.msgid.link/20260917154743.1621-1-shechenglong@xfusion.com --- drivers/gpu/drm/drm_fb_helper.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index d4664ed468b2..0817b639b3fa 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -1724,10 +1724,10 @@ __drm_fb_helper_initial_config_and_unlock(struct drm_fb_helper *fb_helper) ret = drm_fb_helper_single_fb_probe(fb_helper); if (ret < 0) { - if (ret == -EAGAIN) { - fb_helper->deferred_setup = true; + /* modesets are probed but fb_helper->fb isn't; defer restore too */ + fb_helper->deferred_setup = true; + if (ret == -EAGAIN) ret = 0; - } mutex_unlock(&fb_helper->lock); goto err_drm_fb_helper_release_info; -- cgit v1.2.3 From ecce445c6489895463b7ddec2af66338287fee4c Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 18:30:19 -0400 Subject: drm/aspeed: Balance the display clock enable on teardown aspeed_gfx_load() enables the display clock without checking the return value and never disables it on probe failure or removal. devm_clk_get() only releases the clock reference, leaving the enable unbalanced. Use devm_clk_get_enabled() to handle enable failures and automatically disable and unprepare the clock on probe failure or removal. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 4f2a8f5898ec ("drm: Add ASPEED GFX driver") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Assisted-by: OpenAI:GPT-5.6 Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260913223019.89651-1-mhun512@gmail.com Link: https://patch.msgid.link/20260905080426.34224-1-kmehltretter@gmail.com --- drivers/gpu/drm/aspeed/aspeed_gfx_drv.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c index d4577663a1f0..aa36b79d0f6c 100644 --- a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c +++ b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c @@ -189,13 +189,12 @@ static int aspeed_gfx_load(struct drm_device *drm) } reset_control_deassert(priv->rst); - priv->clk = devm_clk_get(drm->dev, NULL); + priv->clk = devm_clk_get_enabled(drm->dev, NULL); if (IS_ERR(priv->clk)) { dev_err(&pdev->dev, - "missing or invalid clk device tree entry"); + "failed to get and enable clock\n"); return PTR_ERR(priv->clk); } - clk_prepare_enable(priv->clk); /* Sanitize control registers */ writel(0, priv->base + CRT_CTRL1); -- cgit v1.2.3 From b41ff8b529278216cba22a5dcdc93fea2a3a9619 Mon Sep 17 00:00:00 2001 From: Maíra Canal Date: Wed, 30 Sep 2026 16:06:50 -0300 Subject: drm/vc4: Free the BO cache size list on teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc4_bo_cache_destroy() purges the cached BOs but never frees the size list, leaking it on every unbind. Fixes: c826a6e10644 ("drm/vc4: Add a BO cache.") Assisted-by: LLM Reviewed-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260930190721.1897264-1-mcanal@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_bo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 49ea2ed0996b..ea1be891ac78 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -1052,6 +1052,7 @@ static void vc4_bo_cache_destroy(struct drm_device *dev, void *unused) cancel_work_sync(&vc4->bo_cache.time_work); vc4_bo_cache_purge(dev); + kfree(vc4->bo_cache.size_list); for (i = 0; i < vc4->num_labels; i++) { if (vc4->bo_labels[i].num_allocated) { -- cgit v1.2.3 From bca45af5998a05f34b13a2ef11e639bac9c62643 Mon Sep 17 00:00:00 2001 From: Maíra Canal Date: Wed, 30 Sep 2026 16:06:51 -0300 Subject: drm/vc4: Use kvmalloc_objs() for the BO cache size list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BO cache size list has one entry per BO page count, so its size scales with the largest BO ever freed. A 64 MiB BO already needs at least an order-6 allocation, which fails under fragmentation and triggers a warn_alloc() splat on BO close: page allocation failure: order:7, mode:0x40cc0(GFP_KERNEL|__GFP_COMP) Call trace: ... warn_alloc+0x118/0x190 __alloc_pages_slowpath+0x4a8/0x520 ... __kmalloc_noprof+0x3ac/0x5d8 vc4_free_object+0x128/0x370 [vc4] drm_gem_object_handle_put_unlocked+0xb8/0x158 [drm] drm_gem_object_release_handle+0x74/0xe8 [drm] drm_gem_handle_delete+0x6c/0xa8 [drm] drm_gem_close_ioctl+0x34/0x50 [drm] The list doesn't need physically contiguous memory. Use kvmalloc_objs() so large lists can fall back to vmalloc(), avoiding the allocation failure. Fixes: c826a6e10644 ("drm/vc4: Add a BO cache.") Reviewed-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260930190721.1897264-2-mcanal@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_bo.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index ea1be891ac78..7b8b3364000f 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -204,7 +204,7 @@ static struct list_head *vc4_get_cache_list_for_size(struct drm_device *dev, struct list_head *new_list; uint32_t i; - new_list = kmalloc_objs(struct list_head, new_size); + new_list = kvmalloc_objs(struct list_head, new_size); if (!new_list) return NULL; @@ -224,7 +224,7 @@ static struct list_head *vc4_get_cache_list_for_size(struct drm_device *dev, for (i = vc4->bo_cache.size_list_size; i < new_size; i++) INIT_LIST_HEAD(&new_list[i]); - kfree(vc4->bo_cache.size_list); + kvfree(vc4->bo_cache.size_list); vc4->bo_cache.size_list = new_list; vc4->bo_cache.size_list_size = new_size; } @@ -1052,7 +1052,7 @@ static void vc4_bo_cache_destroy(struct drm_device *dev, void *unused) cancel_work_sync(&vc4->bo_cache.time_work); vc4_bo_cache_purge(dev); - kfree(vc4->bo_cache.size_list); + kvfree(vc4->bo_cache.size_list); for (i = 0; i < vc4->num_labels; i++) { if (vc4->bo_labels[i].num_allocated) { -- cgit v1.2.3 From e7ed652b1312ffcb9ea2153946615856b5097c78 Mon Sep 17 00:00:00 2001 From: Alan Previn Date: Tue, 29 Sep 2026 12:20:37 -0700 Subject: drm/xe/mmio: Fix xe_mmio_wait32() to honor delay/sleep maximums Check for overflow in udelay use in __xe_mmio_wait32 and avoid growing delays becoming intollerably large by capping the in-loop wait time. v7: - Change assisted-by to "LLM" (Rodrigo). - Move the max-wait macro to outside the function (Rodrigo). v6: - Fix commit message to remove items dropped in v5. Fix the use of MAX_UDELAY_MS for microsecond unit. (Shasiko review). v5: - Simplify: don't introduce additional redesign and use min_t with a backoff to cap wait. (Rodrigo). v4: - Simplify using poll_timeout_us and poll_timeout_us_atomic and update all callers to never use a param for atomic (Jani) v3: - Use div64_s64 kernel API for 32-bit vs 64-bit division. v2: - Fixed checkpatch failure. - Added helper for 64-bit DIV_ROUND_UP on 32-bit CPU (Shasiko review). Fixed bug max range in usleep_range(Shasiko review) Fixes: 5c09bd6ccd41 ("drm/xe/mmio: Move xe_mmio_wait32() to xe_mmio.c") Signed-off-by: Alan Previn Reviewed-by: Rodrigo Vivi Assisted-by: LLM Link: https://patch.msgid.link/20260929192037.2233429-2-alan.previn.teres.alexis@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 70dab9ee7d620e6018a64798aed065d14e9baed1) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio.c b/drivers/gpu/drm/xe/xe_mmio.c index 7fa18dfcb5a2..455a07d79cd9 100644 --- a/drivers/gpu/drm/xe/xe_mmio.c +++ b/drivers/gpu/drm/xe/xe_mmio.c @@ -320,6 +320,7 @@ u64 xe_mmio_read64_2x32(struct xe_mmio *mmio, struct xe_reg reg) return (u64)udw << 32 | ldw; } +#define __XE_MMIO_WAIT_MAX_BACKOFF_100MS (100 * USEC_PER_MSEC) static int __xe_mmio_wait32(struct xe_mmio *mmio, struct xe_reg reg, u32 mask, u32 val, u32 timeout_us, u32 *out_val, bool atomic, bool expect_match) { @@ -350,10 +351,10 @@ static int __xe_mmio_wait32(struct xe_mmio *mmio, struct xe_reg reg, u32 mask, u wait = ktime_us_delta(end, cur); if (atomic) - udelay(wait); + udelay(min_t(s64, wait, (MAX_UDELAY_MS * 1000))); else usleep_range(wait, wait << 1); - wait <<= 1; + wait = min_t(s64, wait << 1, __XE_MMIO_WAIT_MAX_BACKOFF_100MS); } if (ret != 0) { -- cgit v1.2.3 From f7fc61e66041b1f8300cad87005ba84604bfefd9 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 25 Sep 2026 09:00:16 +0100 Subject: drm/xe: Do not emit Wa_16010904313 twice During development of commit e8372edec948 ("drm/xe/xelp: Implement Wa_16010904313") one of the interim patch versions contained an incorrect rebase on top of some upstream changes, which caused Wa_16010904313 to be emitted both from the indirect context and from the workaround batch buffer. As this workaround needs to be emitted from the indirect context for rcs and ccs, but from the workaround batch buffer for bcs, vcs and vecs, we need to pass the location as an input parameter to setup_timestamp_wa() so it can decide whether to emit itself or not. Signed-off-by: Tvrtko Ursulin Fixes: e8372edec948 ("drm/xe/xelp: Implement Wa_16010904313") Cc: Matt Roper Cc: Rodrigo Vivi Reviewed-by: Matt Roper Link: https://patch.msgid.link/20260925080016.64802-1-tvrtko.ursulin@igalia.com Signed-off-by: Rodrigo Vivi [Rodrigo: small adjustments on commit message for check-patch] (cherry picked from commit 38631a7bce195b88814b93bf2b6d3e48c827fef2) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_lrc.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_lrc.c b/drivers/gpu/drm/xe/xe_lrc.c index 35b4e8289b5f..e4388a960490 100644 --- a/drivers/gpu/drm/xe/xe_lrc.c +++ b/drivers/gpu/drm/xe/xe_lrc.c @@ -1099,8 +1099,7 @@ static void xe_lrc_finish(struct xe_lrc *lrc) #define CONTEXT_ACTIVE XE_LRC_CTX_TIMESTAMP_ACTIVE static ssize_t setup_utilization_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, - size_t max_len) + u32 *batch, size_t max_len, bool indirect) { u32 *cmd = batch; @@ -1131,19 +1130,25 @@ static ssize_t setup_utilization_wa(struct xe_lrc *lrc, } static ssize_t setup_timestamp_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, bool indirect) { const u32 ts_addr = __xe_lrc_ctx_timestamp_ggtt_addr(lrc); u32 *cmd = batch; - if (!XE_GT_WA(lrc->gt, 16010904313) || - !(hwe->class == XE_ENGINE_CLASS_RENDER || - hwe->class == XE_ENGINE_CLASS_COMPUTE || - hwe->class == XE_ENGINE_CLASS_COPY || + if (!XE_GT_WA(lrc->gt, 16010904313)) + return 0; + + if (!indirect && + !(hwe->class == XE_ENGINE_CLASS_COPY || hwe->class == XE_ENGINE_CLASS_VIDEO_DECODE || hwe->class == XE_ENGINE_CLASS_VIDEO_ENHANCE)) return 0; + if (indirect && + !(hwe->class == XE_ENGINE_CLASS_RENDER || + hwe->class == XE_ENGINE_CLASS_COMPUTE)) + return 0; + if (xe_gt_WARN_ON(lrc->gt, max_len < 12)) return -ENOSPC; @@ -1169,7 +1174,8 @@ static ssize_t setup_timestamp_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, static ssize_t setup_configfs_post_ctx_restore_bb(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { struct xe_device *xe = gt_to_xe(lrc->gt); const u32 *user_batch; @@ -1198,7 +1204,8 @@ static ssize_t setup_configfs_post_ctx_restore_bb(struct xe_lrc *lrc, static ssize_t setup_configfs_mid_ctx_restore_bb(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { struct xe_device *xe = gt_to_xe(lrc->gt); const u32 *user_batch; @@ -1227,7 +1234,8 @@ static ssize_t setup_configfs_mid_ctx_restore_bb(struct xe_lrc *lrc, static ssize_t setup_invalidate_state_cache_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { u32 *cmd = batch; @@ -1247,7 +1255,8 @@ static ssize_t setup_invalidate_state_cache_wa(struct xe_lrc *lrc, static ssize_t setup_invalidate_auxccs_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { struct xe_gt *gt = lrc->gt; u32 *(*emit)(struct xe_gt *gt, u32 *cmd) = @@ -1264,7 +1273,7 @@ static ssize_t setup_invalidate_auxccs_wa(struct xe_lrc *lrc, struct bo_setup { ssize_t (*setup)(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_size); + u32 *batch, size_t max_size, bool indirect); }; struct bo_setup_state { @@ -1273,6 +1282,7 @@ struct bo_setup_state { struct xe_hw_engine *hwe; size_t max_size; size_t reserve_dw; + bool indirect; unsigned int offset; const struct bo_setup *funcs; unsigned int num_funcs; @@ -1298,7 +1308,8 @@ static int setup_bo(struct bo_setup_state *state) for (size_t i = 0; i < state->num_funcs; i++) { ssize_t len = state->funcs[i].setup(state->lrc, state->hwe, - state->ptr, remain); + state->ptr, remain, + state->indirect); remain -= len; @@ -1404,6 +1415,7 @@ setup_indirect_ctx(struct xe_lrc *lrc, struct xe_hw_engine *hwe) struct bo_setup_state state = { .lrc = lrc, .hwe = hwe, + .indirect = true, .max_size = (63 * 64) /* max 63 cachelines */, .buffer = NULL, .offset = __xe_lrc_indirect_ctx_offset(lrc), -- cgit v1.2.3 From 83d5acca1f4f6132038ea88c72c5844f62e42d44 Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Fri, 2 Oct 2026 12:08:29 +0200 Subject: drm/xe: Fix shrinker accounting double-subtraction on nested external pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xe_bo_pin_external() and xe_bo_unpin_external() support nested pins, since the underlying ttm_bo_pin()/ttm_bo_unpin() maintain a pin_count refcount rather than a boolean. However, the shrinker accounting calls xe_ttm_tt_account_subtract() and xe_ttm_tt_account_add() are invoked unconditionally on every pin_external()/unpin_external() call, instead of only on the transition into and out of the pinned state. An external BO (dma-buf) can be pinned more than once while already pinned, for example when it has multiple attachments/importers, each independently calling xe_bo_pin_external(). Each such nested pin subtracts the BO's pages from the shrinker's accounting again, even though the BO's pages were already removed from consideration by the first pin. Symmetrically, each nested unpin adds them back again before the BO is actually unpinned. This desynchronizes the shrinker's page and object counts from reality, and since they are declared as signed long but interpreted as unsigned long in xe_shrinker_count(), the underflow can turn into an enormous shrinkable/purgeable page count, causing the shrinker to be invoked excessively under memory pressure. Guard the accounting calls with the same pin-count transition checks already used to guard the pinned_link list maintenance, so accounting is only updated on the outermost pin and the final unpin. While centralizing this logic into xe_bo_account_pin()/ xe_bo_account_unpin(), also close a second, related accounting gap affecting imported dma-bufs. For an imported bo's ttm_tt, xe's ttm_tt_populate hook (xe_ttm_tt_populate()) early-returns without calling xe_ttm_tt_account_add(), since TTM_TT_FLAG_EXTERNAL is set without TTM_TT_FLAG_EXTERNAL_MAPPABLE. However, TTM core's ttm_tt_populate() wrapper unconditionally marks the tt as populated on driver-hook success, so ttm_tt_is_populated() still reports true for it. xe_bo_account_pin()/xe_bo_account_unpin() trusted ttm_tt_is_populated() as a proxy for "these pages are accounted for by the shrinker", and would subtract/add pages for such a tt that were never added in the first place, again underflowing the shrinker's counts. This is reachable today via xe_bo_pin_external(), and would have become newly reachable via the fb-pin path once this patch routes __xe_pin_fb_vma() through xe_bo_pin_account() below, so fix it here rather than in a later patch, to avoid a commit in this series that introduces the regression before fixing it. Skip the accounting for imported bos, using the same EXTERNAL && !EXTERNAL_MAPPABLE test xe_ttm_tt_populate()/xe_ttm_tt_unpopulate() use to decide whether to act, via a new xe_ttm_bo_is_imported() helper (hoisted up from its only prior use site). v2: - Fix the critical issue caught by Sashiko AI review: the pin-count transition check introduced here was still bypassed by the fb-pin path (__xe_pin_fb_vma()/__xe_unpin_fb_vma()), which could leave a stale pinned_link entry and a permanent shrinker accounting leak when the fb-unpin performed the true last unpin. Addressed in a separate follow-up patch in this series rather than folded in here, since it is a distinct bug with its own Fixes: tag. - Drop the unneeded local "last_unpin" variable in xe_bo_unpin_external(), restoring the original inline bo->ttm.pin_count == 1 check; it was a no-op rename not used by any of the new accounting helpers, and was going to be deleted again by the follow-up patch anyway. v3: - Exclude imported dma-bufs from the pin/unpin shrinker accounting, fixing another underflow caught by Sashiko AI review. - Add a short comment on the backup-object pin site noting it's already populated/accounted at pin time, addressing (as a false-positive clarification, no code fix needed) a separate Medium-severity accounting-leak concern also caught by Sashiko AI review. Fixes: 00c8efc3180f ("drm/xe: Add a shrinker for xe bos") Cc: Thomas Hellström Cc: Matthew Brost Cc: Rodrigo Vivi Cc: intel-xe@lists.freedesktop.org Cc: # v6.15+ Reviewed-by: Matthew Auld Reviewed-by: Himal Prasad Ghimiray Assisted-by: LLM Signed-off-by: Thomas Hellström Link: https://patch.msgid.link/20261002100830.12297-2-thomas.hellstrom@linux.intel.com (cherry picked from commit 6a117a2bb7ca657e3a19f83f4c06265d38dee06c) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/display/xe_fb_pin.c | 6 +- drivers/gpu/drm/xe/xe_bo.c | 116 +++++++++++++++++++++++++-------- drivers/gpu/drm/xe/xe_bo.h | 2 + 3 files changed, 95 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/xe/display/xe_fb_pin.c b/drivers/gpu/drm/xe/display/xe_fb_pin.c index 73469ea5f333..f1dbdc802df8 100644 --- a/drivers/gpu/drm/xe/display/xe_fb_pin.c +++ b/drivers/gpu/drm/xe/display/xe_fb_pin.c @@ -356,7 +356,7 @@ static struct i915_vma *__xe_pin_fb_vma(struct drm_gem_object *obj, bool is_dpt, drm_exec_retry_on_contention(&exec); xe_validation_retry_on_oom(&ctx, &ret); if (!ret) - ttm_bo_pin(&bo->ttm); + xe_bo_pin_account(bo); } if (ret) goto err; @@ -373,7 +373,7 @@ static struct i915_vma *__xe_pin_fb_vma(struct drm_gem_object *obj, bool is_dpt, err_unpin: ttm_bo_reserve(&bo->ttm, false, false, NULL); - ttm_bo_unpin(&bo->ttm); + xe_bo_unpin_account(bo); ttm_bo_unreserve(&bo->ttm); err: kfree(vma); @@ -393,7 +393,7 @@ static void __xe_unpin_fb_vma(struct i915_vma *vma) xe_ggtt_node_remove(vma->node, false); ttm_bo_reserve(&vma->bo->ttm, false, false, NULL); - ttm_bo_unpin(&vma->bo->ttm); + xe_bo_unpin_account(vma->bo); ttm_bo_unreserve(&vma->bo->ttm); kfree(vma); } diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index b4921a627ff3..f85d5f9b5fce 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -455,6 +455,85 @@ static void xe_ttm_tt_account_subtract(struct xe_device *xe, struct ttm_tt *tt) xe_shrinker_mod_pages(xe->mem.shrinker, -(long)tt->num_pages, 0); } +static bool xe_ttm_bo_is_imported(struct ttm_buffer_object *tbo) +{ + dma_resv_assert_held(tbo->base.resv); + + return tbo->ttm && + (tbo->ttm->page_flags & (TTM_TT_FLAG_EXTERNAL | TTM_TT_FLAG_EXTERNAL_MAPPABLE)) == + TTM_TT_FLAG_EXTERNAL; +} + +/* + * Account @bo's pages as pinned for the shrinker. Removes @bo's pages + * from the shrinker's shrinkable / purgeable counts on the transition + * from unpinned to pinned. Must be called with @bo's dma-resv held, + * after &ttm_buffer_object.pin_count has been incremented by + * ttm_bo_pin(). Safe to call unconditionally regardless of which pin + * path (kernel, external, framebuffer, backup object, ...) is pinning + * @bo, since it only acts on the true 0->1 pin_count transition. + * + * Imported bos (xe_ttm_bo_is_imported()) are excluded: for those, + * xe_ttm_tt_populate() never actually populates the tt or calls + * xe_ttm_tt_account_add(), but TTM core's ttm_tt_populate() still + * unconditionally marks the tt as populated on driver-hook success. + * Relying on ttm_tt_is_populated() for these would subtract pages that + * were never added, underflowing the shrinker's counts. + */ +static void xe_bo_account_pin(struct xe_bo *bo) +{ + struct xe_device *xe = xe_bo_device(bo); + + if (bo->ttm.pin_count == 1 && bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm) && + !xe_ttm_bo_is_imported(&bo->ttm)) + xe_ttm_tt_account_subtract(xe, bo->ttm.ttm); +} + +/* + * Counterpart to xe_bo_account_pin(). Must be called with @bo's dma-resv + * held, before &ttm_buffer_object.pin_count is decremented by + * ttm_bo_unpin(), so that the check against the true 1->0 transition sees + * the pin count that is about to be released. See xe_bo_account_pin() for + * why imported bos are excluded. + */ +static void xe_bo_account_unpin(struct xe_bo *bo) +{ + struct xe_device *xe = xe_bo_device(bo); + + if (bo->ttm.pin_count == 1 && bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm) && + !xe_ttm_bo_is_imported(&bo->ttm)) + xe_ttm_tt_account_add(xe, bo->ttm.ttm); +} + +/** + * xe_bo_pin_account() - Pin a bo and account its pages for the shrinker + * @bo: The buffer object to pin. + * + * Pins @bo via ttm_bo_pin() and updates the shrinker's shrinkable / + * purgeable page accounting to match. Must be called with @bo's dma-resv + * held. Safe to call regardless of which pin path (kernel, external, + * framebuffer, backup object, ...) is pinning @bo. + */ +void xe_bo_pin_account(struct xe_bo *bo) +{ + ttm_bo_pin(&bo->ttm); + xe_bo_account_pin(bo); +} + +/** + * xe_bo_unpin_account() - Unpin a bo and account its pages for the shrinker + * @bo: The buffer object to unpin. + * + * Counterpart to xe_bo_pin_account(). Updates the shrinker's shrinkable / + * purgeable page accounting to match, then unpins @bo via ttm_bo_unpin(). + * Must be called with @bo's dma-resv held. + */ +void xe_bo_unpin_account(struct xe_bo *bo) +{ + xe_bo_account_unpin(bo); + ttm_bo_unpin(&bo->ttm); +} + static void update_global_total_pages(struct ttm_device *ttm_dev, long num_pages) { @@ -1389,7 +1468,8 @@ int xe_bo_notifier_prepare_pinned(struct xe_bo *bo) } backup->parent_obj = xe_bo_get(bo); /* Released by bo_destroy */ - ttm_bo_pin(&backup->ttm); + /* Note: XE_BO_FLAG_SYSTEM resolves to XE_PL_TT so already populated. */ + xe_bo_pin_account(backup); bo->backup_obj = backup; } @@ -1409,7 +1489,7 @@ int xe_bo_notifier_unprepare_pinned(struct xe_bo *bo) { xe_bo_lock(bo, false); if (bo->backup_obj) { - ttm_bo_unpin(&bo->backup_obj->ttm); + xe_bo_unpin_account(bo->backup_obj); xe_bo_put(bo->backup_obj); bo->backup_obj = NULL; } @@ -1620,7 +1700,7 @@ out_backup: xe_bo_vunmap(backup); if (!bo->backup_obj) { if (xe_bo_is_pinned(backup)) - ttm_bo_unpin(&backup->ttm); + xe_bo_unpin_account(backup); xe_bo_put(backup); } out_unlock_bo: @@ -2012,15 +2092,6 @@ static vm_fault_t xe_err_to_fault_t(int err) return VM_FAULT_SIGBUS; } -static bool xe_ttm_bo_is_imported(struct ttm_buffer_object *tbo) -{ - dma_resv_assert_held(tbo->base.resv); - - return tbo->ttm && - (tbo->ttm->page_flags & (TTM_TT_FLAG_EXTERNAL | TTM_TT_FLAG_EXTERNAL_MAPPABLE)) == - TTM_TT_FLAG_EXTERNAL; -} - static vm_fault_t xe_bo_cpu_fault_fastpath(struct vm_fault *vmf, struct xe_device *xe, struct xe_bo *bo, bool needs_rpm) { @@ -3141,12 +3212,13 @@ uint64_t vram_region_gpu_offset(struct ttm_resource *res) int xe_bo_pin_external(struct xe_bo *bo, bool in_place, struct drm_exec *exec) { struct xe_device *xe = xe_bo_device(bo); + bool first_pin = !xe_bo_is_pinned(bo); int err; xe_assert(xe, !bo->vm); xe_assert(xe, xe_bo_is_user(bo)); - if (!xe_bo_is_pinned(bo)) { + if (first_pin) { if (!in_place) { err = xe_bo_validate(bo, NULL, false, exec); if (err) @@ -3158,9 +3230,7 @@ int xe_bo_pin_external(struct xe_bo *bo, bool in_place, struct drm_exec *exec) spin_unlock(&xe->pinned.lock); } - ttm_bo_pin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_subtract(xe, bo->ttm.ttm); + xe_bo_pin_account(bo); /* * FIXME: If we always use the reserve / unreserve functions for locking @@ -3216,9 +3286,7 @@ int xe_bo_pin(struct xe_bo *bo, struct drm_exec *exec) spin_unlock(&xe->pinned.lock); } - ttm_bo_pin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_subtract(xe, bo->ttm.ttm); + xe_bo_pin_account(bo); /* * FIXME: If we always use the reserve / unreserve functions for locking @@ -3252,9 +3320,7 @@ void xe_bo_unpin_external(struct xe_bo *bo) list_del_init(&bo->pinned_link); spin_unlock(&xe->pinned.lock); - ttm_bo_unpin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_add(xe, bo->ttm.ttm); + xe_bo_unpin_account(bo); /* * FIXME: If we always use the reserve / unreserve functions for locking @@ -3279,14 +3345,12 @@ void xe_bo_unpin(struct xe_bo *bo) if (bo->backup_obj) { if (xe_bo_is_pinned(bo->backup_obj)) - ttm_bo_unpin(&bo->backup_obj->ttm); + xe_bo_unpin_account(bo->backup_obj); xe_bo_put(bo->backup_obj); bo->backup_obj = NULL; } } - ttm_bo_unpin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_add(xe, bo->ttm.ttm); + xe_bo_unpin_account(bo); } /** diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h index 152bfcffe399..53a701d3d242 100644 --- a/drivers/gpu/drm/xe/xe_bo.h +++ b/drivers/gpu/drm/xe/xe_bo.h @@ -229,6 +229,8 @@ int xe_bo_pin_external(struct xe_bo *bo, bool in_place, struct drm_exec *exec); int xe_bo_pin(struct xe_bo *bo, struct drm_exec *exec); void xe_bo_unpin_external(struct xe_bo *bo); void xe_bo_unpin(struct xe_bo *bo); +void xe_bo_pin_account(struct xe_bo *bo); +void xe_bo_unpin_account(struct xe_bo *bo); int xe_bo_validate(struct xe_bo *bo, struct xe_vm *vm, bool allow_res_evict, struct drm_exec *exec); -- cgit v1.2.3 From cf238c067eaae1cbfb00738e29219b9568d2313e Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Fri, 2 Oct 2026 12:08:30 +0200 Subject: drm/xe: Fix stale pinned_link entry when fb-pin performs the final unpin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xe_bo_pin_external() and xe_bo_unpin_external() maintain the bo's pinned_link list membership in xe->pinned.late.external, based on whether the current call is the outermost pin or the final unpin. However, the same external bo's pin_count can also be raised and lowered directly by __xe_pin_fb_vma()/__xe_unpin_fb_vma(), which pin the bo as a display scanout buffer without going through xe_bo_pin_external()/xe_bo_unpin_external() at all, and have no notion of, or ownership over, pinned_link. If a bo is pinned both externally (e.g. dma-buf export) and as an fb, and the external unpin happens first, xe_bo_unpin_external() correctly observes pin_count > 1 and leaves the bo on pinned_link. When the fb unpin later performs the true last unpin (pin_count 1 -> 0), it never touches pinned_link, leaving the bo linked on xe->pinned.late.external indefinitely. Once the bo is subsequently freed, this stale list entry points into freed memory, corrupting the list and risking a use-after-free the next time the list is walked or spliced. The backup object pin/unpin sites in xe_bo_notifier_prepare_pinned()/ xe_bo_notifier_unprepare_pinned() have the same bypass characteristic, though they never add their bo to a pinned list, so are not affected by this particular list-corruption issue. Move the pinned_link removal into xe_bo_account_unpin(), which already runs on every unpin path (kernel, external, framebuffer, backup object) right before the true 1 -> 0 pin_count transition. Since list_del_init() only operates on the node itself, this removal is list-agnostic and safe to perform regardless of which list (external or kernel_bo_present) the bo happens to be linked on, or which code path is performing the final unpin. Drop the now-redundant explicit list_del_init() calls in xe_bo_unpin_external() and xe_bo_unpin(). v2: - New patch v3: - Rebased on changes to previous patch. Fixes: 44e694958b95 ("drm/xe/display: Implement display support") Cc: Maarten Lankhorst Cc: Rodrigo Vivi Cc: intel-xe@lists.freedesktop.org Cc: # v6.8+ Reviewed-by: Matthew Auld Reviewed-by: Himal Prasad Ghimiray Assisted-by: LLM Signed-off-by: Thomas Hellström Link: https://patch.msgid.link/20261002100830.12297-3-thomas.hellstrom@linux.intel.com (cherry picked from commit ca58d36a1f93af586ee93b340c9f9fd81312cc01) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_bo.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index f85d5f9b5fce..7b95ce0445a9 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -495,12 +495,27 @@ static void xe_bo_account_pin(struct xe_bo *bo) * ttm_bo_unpin(), so that the check against the true 1->0 transition sees * the pin count that is about to be released. See xe_bo_account_pin() for * why imported bos are excluded. + * + * On the true last unpin, also removes @bo from whichever pinned-bo list + * (external or kernel_bo_present) it may currently be linked on, since a + * bo's final unpin can happen through a pin path (e.g. framebuffer, + * backup object) that has no notion of, or ownership over, that list. + * This is safe and list-agnostic: list_del_init() only needs the node + * itself, not knowledge of which list it is threaded through, and is a + * no-op if @bo is not linked. */ static void xe_bo_account_unpin(struct xe_bo *bo) { struct xe_device *xe = xe_bo_device(bo); + bool last_unpin = bo->ttm.pin_count == 1; - if (bo->ttm.pin_count == 1 && bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm) && + if (last_unpin && !list_empty(&bo->pinned_link)) { + spin_lock(&xe->pinned.lock); + list_del_init(&bo->pinned_link); + spin_unlock(&xe->pinned.lock); + } + + if (last_unpin && bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm) && !xe_ttm_bo_is_imported(&bo->ttm)) xe_ttm_tt_account_add(xe, bo->ttm.ttm); } @@ -3315,11 +3330,6 @@ void xe_bo_unpin_external(struct xe_bo *bo) xe_assert(xe, xe_bo_is_pinned(bo)); xe_assert(xe, xe_bo_is_user(bo)); - spin_lock(&xe->pinned.lock); - if (bo->ttm.pin_count == 1 && !list_empty(&bo->pinned_link)) - list_del_init(&bo->pinned_link); - spin_unlock(&xe->pinned.lock); - xe_bo_unpin_account(bo); /* @@ -3338,10 +3348,7 @@ void xe_bo_unpin(struct xe_bo *bo) xe_assert(xe, xe_bo_is_pinned(bo)); if (mem_type_is_vram(place->mem_type) || bo->flags & XE_BO_FLAG_GGTT) { - spin_lock(&xe->pinned.lock); xe_assert(xe, !list_empty(&bo->pinned_link)); - list_del_init(&bo->pinned_link); - spin_unlock(&xe->pinned.lock); if (bo->backup_obj) { if (xe_bo_is_pinned(bo->backup_obj)) -- cgit v1.2.3 From 71ec70ea26e399eb8371fe9d2d5bf5dab09570fb Mon Sep 17 00:00:00 2001 From: Sajal Gupta Date: Tue, 22 Sep 2026 14:10:08 +0530 Subject: drm/gud: don't keep a connector without a CRTC as connector_state If the 'for_each_new_connector_in_state()' loop finishes without hitting the break, connector_state is left pointing at the last connector visited, which has no CRTC, instead of staying NULL. That skips the "if (!connector_state)" fallback below the loop, which looks up the active connector when the state contains none. The function then carries on with a connector that is not driven. Fix by resetting connector_state to NULL for each connector visited that has no CRTC, so that it is NULL when none was found and the fallback runs. Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260902123254.36987-1-sajal2005gupta@gmail.com?part=1 Signed-off-by: Sajal Gupta Acked-by: Ruben Wauters Cc: Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260922084026.15481-1-sajal2005gupta@gmail.com --- drivers/gpu/drm/gud/gud_pipe.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index aa7792966287..5aa5ac2f3727 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -507,6 +507,7 @@ int gud_plane_atomic_check(struct drm_plane *plane, for_each_new_connector_in_state(state, connector, connector_state, i) { if (connector_state->crtc) break; + connector_state = NULL; } /* -- cgit v1.2.3 From ebe162db655881a38de43301151ff7ac940602b5 Mon Sep 17 00:00:00 2001 From: Krzysztof Karas Date: Fri, 11 Sep 2026 06:11:45 +0000 Subject: drm/i915/gem: Prevent overstepping exec array boundary It is possible for eb_relocate_parse_slow() to call kvfree() on entries outside the original exec array during following scenario inside eb_relocate_parse_slow(): 1) eb_copy_relocations() allocates as many as eb->buffer_count copies for exec entries; 2) eb_parse() appends up to two VMAs, right after incrementing eb->buffer_count; 3) cleanup under "out" label uses this incremented buffer_count to release relocation pointers from exec array via kvfree(), not from VMA array. To amend this problem, ensure that relocation cleanup uses the original exec object count, excluding the VMAs appended by the command parser. Fixes: 8e4ba491b0ba ("drm/i915: Parse command buffer earlier in eb_relocate(slow)") Signed-off-by: Krzysztof Karas Assisted-by: GitHub-Copilot:gpt-6-astra Cc: stable@vger.kernel.org # 5.10+ Reviewed-by: Andi Shyti Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260911061145.3535489-1-krzysztof.karas@intel.com (cherry picked from commit 2a0158925fb943c7ba8f3216e2734952128defc9) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index 1f303d4eaa4d..d5aadced7da0 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -1766,6 +1766,7 @@ static int eb_reinit_userptr(struct i915_execbuffer *eb) static noinline int eb_relocate_parse_slow(struct i915_execbuffer *eb) { + const unsigned int count = eb->buffer_count; bool have_copy = false; struct eb_vma *ev; int err = 0; @@ -1868,7 +1869,6 @@ err: out: if (have_copy) { - const unsigned int count = eb->buffer_count; unsigned int i; for (i = 0; i < count; i++) { -- cgit v1.2.3 From 5346b1cec6afa4f9b4b49a9f84e3cfef95a3f7bb Mon Sep 17 00:00:00 2001 From: Krzysztof Karas Date: Thu, 10 Sep 2026 08:13:29 +0000 Subject: drm/i915/gt: Unmask interrupts only when ACTIVE is true There is a small race window between intel_rps_boost() reading a stale ACTIVE state and queuing rps->work, where ACTIVE may be changed via asynchronous calls triggered by wakeref drop. This means that rps_work() could be triggered after the GT has been parked, so unmasking RPS PM interrupts would happen without holding PM wakelock. This sequence of events may be observed during live_rc6_ctx_wa() selftest: 1) live_rc6_ctx_wa() -> intel_engine_pm_put() -> wakeref_put() ASYNC1) wakeref_put() callback -> __engine_park() -> intel_gt_pm_put_async() -> intel_gt_pm_put_async_untracked() -> intel_wakeref_put_async() ASYNC2) wakeref_put_async() callback -> __gt_park() -> intel_rps_park() -> intel_rps_clear_active() [clear ACTIVE] WITH INTERRUPTS: -> rps_disable_interrupts() -> cancel_work_sync(rps->work) -> rps_reset_interrupts() [rps->pm_iir = 0] WITHOUT INTERRUPTS (TIMER mode): -> rps_stop_timer() -> cancel_work_sync(rps->work) 2) live_rc6_ctx_wa() -> intel_gt_wait_for_idle() -> intel_gt_retire_requests_timeout() -> dma_fence_wait_timeout() -> ops->wait() [i915_fence_wait()] -> i915_request_wait_timeout() -> intel_rps_boost() -> intel_rps_is_active() [check ACTIVE] [OPEN RACE WINDOW] -> queue_work(rps->work) -> retire_requests() -> i915_request_retire() -> intel_rps_dec_waiters() [from 1 to 0] If ASYNC1 and ASYNC2 would execute during [OPEN RACE WINDOW], then: 3) rps_work() executes from the workqueue and may satisfy "(!pm_iir && !client_boost)" test (in TIMER mode rps->pm_iir is zeroed by an earlier rps_work() call or never gets a new value after previous intel_rps_unpark() event), so it jumps to "out" label, bypassing intel_rps_is_active() guard and executes gen6_gt_pm_unmask_irq(); 4) GT is parked and runtime PM wakelock is dropped, so only a raw reference in __gt_park() -> intel_display_power_put_async() remains for 100ms, then the MMIO write in gen6_gt_pm_unmask_irq() detects missing wakelock via assert_rpm_wakelock_held() and emits "RPM wakelock ref not held during HW access" tainting kernel. Commit 3e7abf814193 ("drm/i915: Extract GT render power state management") dropped the "if (rps->interrupts_enabled)" guard that gen6_pm_rps_work() had around this unmask in i915_irq.c. INTEL_RPS_ACTIVE brackets the same window, since it is set in intel_rps_unpark() before rps_enable_interrupts() and rps_start_timer() and cleared in intel_rps_park() before rps_stop_timer() and rps_disable_interrupts(). Skip unmasking when RPS is no longer active (reaching unmask operation is independent of INTERRUPT mode). This does not prevent RPS operation from resuming on the next unpark. In interrupt mode, intel_rps_unpark() calls rps_enable_interrupts(), which enables and unmasks the PM interrupts. In timer mode, it calls rps_start_timer(), restarting periodic engine-busyness evaluation. The timer generates software RPS events and queues rps_work() directly, independently of PM interrupt delivery. Neither mode requires the late worker to unmask interrupts while the GT is parked Fixes: 3e7abf814193 ("drm/i915: Extract GT render power state management") Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/16562 Cc: stable@vger.kernel.org # v5.5+ Assisted-by: GitHub-Copilot:claude-opus-5 Assisted-by: GitHub-Copilot:gpt-6-astra Signed-off-by: Krzysztof Karas Reviewed-by: Andi Shyti Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260910081329.3300417-1-krzysztof.karas@intel.com (cherry picked from commit ca4a6deadad15b91ee083afe445fa6bdbfb68206) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/gt/intel_rps.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gt/intel_rps.c b/drivers/gpu/drm/i915/gt/intel_rps.c index a33b19c04737..0f9b4f1dd3cb 100644 --- a/drivers/gpu/drm/i915/gt/intel_rps.c +++ b/drivers/gpu/drm/i915/gt/intel_rps.c @@ -1904,7 +1904,8 @@ static void rps_work(struct work_struct *work) out: spin_lock_irq(gt->irq_lock); - gen6_gt_pm_unmask_irq(gt, rps->pm_events); + if (intel_rps_is_active(rps)) + gen6_gt_pm_unmask_irq(gt, rps->pm_events); spin_unlock_irq(gt->irq_lock); } -- cgit v1.2.3 From f0a4447789ef28ba1aded2db19543d69c640b5e5 Mon Sep 17 00:00:00 2001 From: Mallesh Koujalagi Date: Thu, 24 Sep 2026 11:39:07 +0530 Subject: drm/xe/sysctrl: Fix mailbox header handling The mailbox header was defined as __le32, which required cpu_to_le32() and le32_to_cpu() conversions whenever it was write or read. These conversions are unnecessary because the header is always handled as a 32-bit value, not as raw bytes. It is written with xe_mmio_write32() and read with xe_mmio_read32(), which already handle any required byte-order conversion through the underlying writel()/readl() accessors. As a result, the header value is already in CPU byte order when used by the driver. Keeping the extra conversions had no effect on little-endian systems and could cause an incorrect on big-endian systems. So use u32 for the mailbox header and remove the unnecessary endianness conversions. Fixes: 1f95f618182b ("drm/xe/xe_sysctrl: Add System Controller mailbox communication support") Suggested-by: Michal Wajdeczko Signed-off-by: Mallesh Koujalagi Tested-by: Anoop Vijay Reviewed-by: Umesh Nerlige Ramappa Signed-off-by: Umesh Nerlige Ramappa Link: https://patch.msgid.link/20260924060900.146167-18-mallesh.koujalagi@intel.com (cherry picked from commit 5137b391599579371c32f555958d3cf55e19bd88) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_sysctrl_mailbox.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c index 72baf1aa4b3a..a0179e62abaa 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c +++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c @@ -19,20 +19,20 @@ #include "xe_sysctrl_mailbox_types.h" struct xe_sysctrl_mailbox_msg_hdr { - __le32 data; -} __packed; + u32 data; +}; #define XE_SYSCTRL_HDR_GROUP_ID(hdr) \ - FIELD_GET(SYSCTRL_HDR_GROUP_ID_MASK, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_GROUP_ID_MASK, (hdr)->data) #define XE_SYSCTRL_HDR_COMMAND(hdr) \ - FIELD_GET(SYSCTRL_HDR_COMMAND_MASK, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_COMMAND_MASK, (hdr)->data) #define XE_SYSCTRL_HDR_IS_RESPONSE(hdr) \ - FIELD_GET(SYSCTRL_HDR_IS_RESPONSE, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_IS_RESPONSE, (hdr)->data) #define XE_SYSCTRL_HDR_RESULT(hdr) \ - FIELD_GET(SYSCTRL_HDR_RESULT_MASK, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_RESULT_MASK, (hdr)->data) static bool sysctrl_wait_bit_clear(struct xe_sysctrl *sc, u32 bit_mask, unsigned int timeout_ms) @@ -126,8 +126,8 @@ static int sysctrl_prepare_command(struct xe_device *xe, return -ENOMEM; hdr = (struct xe_sysctrl_mailbox_msg_hdr *)buffer; - hdr->data = cpu_to_le32(FIELD_PREP(SYSCTRL_HDR_GROUP_ID_MASK, group_id) | - FIELD_PREP(SYSCTRL_HDR_COMMAND_MASK, command)); + hdr->data = FIELD_PREP(SYSCTRL_HDR_GROUP_ID_MASK, group_id) | + FIELD_PREP(SYSCTRL_HDR_COMMAND_MASK, command); if (data_in && data_in_len) memcpy(buffer + sizeof(*hdr), data_in, data_in_len); -- cgit v1.2.3 From 611857adc9e7b0ee1239a0baf38498922ca5a12e Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Mon, 5 Oct 2026 20:04:14 -0400 Subject: drm/xe/i2c: cancel the client work on remove xe_i2c_notifier() stores the DesignWare adapter in i2c->adapter and schedules i2c->work when the adapter is registered under the xe I2C platform device, and xe_i2c_client_work() then instantiates the AMC client device on that adapter. xe_i2c_remove() tears down the AMC, unregisters the client devices, the bus notifier and the adapter platform device, but it never drains i2c->work. A work item that is still queued or running when the adapter is unregistered dereferences i2c->adapter in i2c_new_client_device() after platform_device_unregister() has released the adapter. The work item is also embedded in the devm-allocated struct xe_i2c, so a work item still queued after the drm device devm unwind frees that allocation runs its callback on freed memory. The bus notifier is the only thing that schedules this work, and it is unregistered after the client devices. An instance that is still queued when the teardown runs can therefore write i2c->client[XE_I2C_CLIENT_AMC] while the loop is unregistering and clearing the same array, and an AMC client it instantiates late is only cleaned up by the adapter's own child sweep in i2c_del_adapter(). Move bus_unregister_notifier() in front of the client teardown loop and cancel the work right after it, so no new instance can be scheduled and a queued instance is drained before the client array is touched. A running instance still finds a live adapter, since the adapter is unregistered later. Fixes: f0e53aadd702 ("drm/xe: Support for I2C attached MCUs") Link: https://lore.kernel.org/intel-xe/20260912085932.101598-1-fanwu01@zju.edu.cn/ Cc: stable@vger.kernel.org Assisted-by: LLM, in-house static analysis tool Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260928013030.612592-1-fanwu01@zju.edu.cn Link: https://patch.msgid.link/20261006000413.318561-2-rodrigo.vivi@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit a23f27d5ec11c3f418c5e5531eea34ccda2b5b20) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_i2c.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index f4f381988289..b82caaff80d7 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -324,12 +324,15 @@ static void xe_i2c_remove(void *data) xe_i2c_irq_reset(xe); xe_amc_exit(i2c); + /* Stop the notifier from arming the client work before teardown. */ + bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); + cancel_work_sync(&i2c->work); + for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) { i2c_unregister_device(i2c->client[i]); i2c->client[i] = NULL; } - bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); xe_i2c_unregister_adapter(i2c); xe->i2c = NULL; } -- cgit v1.2.3 From 7744262f5d63f9faa4d59dec127ae1c8b2d56495 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 29 Sep 2026 17:32:32 -0400 Subject: drm/amd/display: Fix direct scanout alpha on some DRM_FORMATs [Why] DM was declaring it's own list of has_alpha DRM formats to check against for enabling alpha blending, when the format->has_alpha already does that for us. The self-declared list missed some has_alpha formats, causing direct-scanout planes with such formats to appear opaque. [How] Drop the self-declared list, instead use the format->has_alpha field directly. Signed-off-by: Leo Li Reviewed-by: Harry Wentland Signed-off-by: Alex Deucher (cherry picked from commit 9ecdac91ed43d836683d5daa467f4907405420e2) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index e13b96358208..a2b8f7033616 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -112,25 +112,7 @@ void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state if (plane_state->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI || plane_state->pixel_blend_mode == DRM_MODE_BLEND_COVERAGE) { - static const uint32_t alpha_formats[] = { - DRM_FORMAT_ARGB8888, - DRM_FORMAT_RGBA8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_ARGB2101010, - DRM_FORMAT_ABGR2101010, - DRM_FORMAT_ARGB16161616, - DRM_FORMAT_ABGR16161616, - DRM_FORMAT_ARGB16161616F, - }; - uint32_t format = plane_state->fb->format->format; - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(alpha_formats); ++i) { - if (format == alpha_formats[i]) { - *per_pixel_alpha = true; - break; - } - } + *per_pixel_alpha = plane_state->fb->format->has_alpha; if (*per_pixel_alpha && plane_state->pixel_blend_mode == DRM_MODE_BLEND_COVERAGE) *pre_multiplied_alpha = false; -- cgit v1.2.3 From 7f7c88dbf4d543f6703e53bdeed380e886733167 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Wed, 30 Sep 2026 14:21:19 +0800 Subject: drm/amdgpu/userq: only accept doorbell BOs as queue doorbell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_userq_get_doorbell_index() pins any BO passed as doorbell_handle into the doorbell domain. For a regular BO, TTM moves it with ttm_bo_move_null() and its contents are lost. Since an importer on the same device gets the exporter's GEM object, a client can do this to a buffer another client shared with it. Reject BOs that were not created in the doorbell domain. v2: drop the error message, use a local abo variable (Christian) Fixes: f09c1e6077ab ("drm/amdgpu: generate doorbell index for userqueue") Reviewed-by: Christian König Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit f7497bbc19db4821c62b9d97b31c9086feb2d917) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index cc8e7af18834..9316423f9340 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -472,6 +472,7 @@ amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, u64 doorbell_index; struct drm_gem_object *gobj; struct amdgpu_userq_obj *db_obj = db_info->db_obj; + struct amdgpu_bo *abo; int r, db_size; gobj = drm_gem_object_lookup(filp, db_info->doorbell_handle); @@ -480,7 +481,17 @@ amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, return -EINVAL; } - db_obj->obj = amdgpu_bo_ref(gem_to_amdgpu_bo(gobj)); + /* + * Pinning a regular BO into the doorbell domain would discard its + * contents, possibly those of a buffer shared by another client. + */ + abo = gem_to_amdgpu_bo(gobj); + if (!(abo->preferred_domains & AMDGPU_GEM_DOMAIN_DOORBELL)) { + drm_gem_object_put(gobj); + return -EINVAL; + } + + db_obj->obj = amdgpu_bo_ref(abo); drm_gem_object_put(gobj); r = amdgpu_bo_reserve(db_obj->obj, true); -- cgit v1.2.3 From 76ceb6ed610cbf14341a9ba78c5c0b2199b9030a Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Wed, 30 Sep 2026 14:21:19 +0800 Subject: drm/amdgpu/userq: return the memdup_user() error for the user MQD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mes_userq_mqd_create() returns -ENOMEM when copying the user MQD fails, even though memdup_user() returns -EFAULT for a bad pointer or -EINTR when interrupted by a signal. Return PTR_ERR() instead. While at it drop the error messages. Neither case is a driver error and userspace shouldn't be able to spam the log with invalid IOCTL parameters. v2: drop the error messages (Christian) Fixes: 2c695d7c0720 ("drm/amdgpu: enable compute/gfx usermode queue") Fixes: 543b61453774 ("drm/amdgpu: enable SDMA usermode queues") Reviewed-by: Christian König Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit 49a443fcb829be9fc780f10850652ddea2fe81f0) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index 14a5abe42d1f..8d5dcaf4fea6 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -361,8 +361,7 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, compute_mqd = memdup_user(u64_to_user_ptr(mqd_user->mqd), mqd_user->mqd_size); if (IS_ERR(compute_mqd)) { - DRM_ERROR("Failed to read user MQD\n"); - r = -ENOMEM; + r = PTR_ERR(compute_mqd); goto free_mqd; } @@ -406,8 +405,7 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, mqd_gfx_v11 = memdup_user(u64_to_user_ptr(mqd_user->mqd), mqd_user->mqd_size); if (IS_ERR(mqd_gfx_v11)) { - DRM_ERROR("Failed to read user MQD\n"); - r = -ENOMEM; + r = PTR_ERR(mqd_gfx_v11); goto free_mqd; } @@ -451,8 +449,7 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, mqd_sdma_v11 = memdup_user(u64_to_user_ptr(mqd_user->mqd), mqd_user->mqd_size); if (IS_ERR(mqd_sdma_v11)) { - DRM_ERROR("Failed to read sdma user MQD\n"); - r = -ENOMEM; + r = PTR_ERR(mqd_sdma_v11); goto free_mqd; } -- cgit v1.2.3 From 2e54a92e0136c3ff5c7b542b95bf0940ad6687bf Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 12:30:52 -0400 Subject: drm/amd/display: disable self-refresh on tearing flips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why & How] Immediate (tearing) flips breaks self refresh state machine assumptions, so disable PSR/Replay for them. Tested-by: Timur Kristóf Reviewed-by: Harry Wentland Signed-off-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit acbdd213b37e095f15685f3e3ee6d35defa05616) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 1b42c4606002..47a0ff2ed035 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3708,6 +3708,13 @@ static void amdgpu_dm_enable_self_refresh(struct amdgpu_display_manager *dm, struct amdgpu_dm_connector *aconn = (struct amdgpu_dm_connector *)acrtc_state->stream->dm_stream_context; + if (!acrtc_state->base.async_flip) { + amdgpu_dm_psr_set_event(dm, acrtc_state->stream, false, + psr_event_immediate_flip, false); + amdgpu_dm_replay_set_event(dm, acrtc_state->stream, false, + replay_event_immediate_flip, false); + } + /* Decrement skip count when SR is enabled and we're doing fast updates. */ if (acrtc_state->update_type == UPDATE_TYPE_FAST && (psr->psr_feature_enabled || pr->replay_feature_enabled)) { @@ -4118,6 +4125,18 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); } mutex_lock(&dm->dc_lock); + /* + * Tearing (immediate) flips cannot work with panel self-refresh + * features. The event is cleared by + * amdgpu_dm_enable_self_refresh() once async flips stop. + */ + if (immediate_flip) { + dc_exit_ips_for_hw_access(dm->dc); + amdgpu_dm_psr_set_event(dm, acrtc_state->stream, true, + psr_event_immediate_flip, true); + amdgpu_dm_replay_set_event(dm, acrtc_state->stream, true, + replay_event_immediate_flip, true); + } update_planes_and_stream_adapter(dm->dc, acrtc_state->update_type, planes_count, -- cgit v1.2.3 From 1a6023edfbb5673f55fab6348f135fb3b1428ea5 Mon Sep 17 00:00:00 2001 From: Vitaly Prosyak Date: Tue, 29 Sep 2026 20:18:26 -0400 Subject: drm/amdgpu: serialize vblank counter reads against GPU reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The vblank counter callback reads display registers and scanout position without holding reset_domain->sem. During GPU recovery, display commit work can call the callback while reset owns the semaphore for writing. The hardware access then triggers the lockdep_assert_held() in amdgpu_device_skip_hw_access() and sets TAINT_WARN when lockdep is enabled. Try to acquire the reset semaphore for reading before accessing the hardware, and hold it across the counter and scanout-position reads. If reset is already in progress, return DRM's cached software vblank count instead. This avoids blocking behind reset and avoids accessing display hardware while it is being reset. The cached count is less accurate than the hardware count, but is used only when reset prevents a safe hardware read. This addresses the reset-time warning from this vblank path; it does not suppress or fix unrelated lockdep warnings. Cc: Christian König Cc: Alex Deucher Cc: Harry Wentland Cc: Sunpeng Li Signed-off-by: Vitaly Prosyak Reviewed-by: Sunpeng Li Signed-off-by: Alex Deucher (cherry picked from commit 51a4359f7431a4be5e42803c8bdaf7e0e5be2934) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 242c48e85912..7c081b338fe2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "amdgpu_uvd.h" #include "amdgpu_vce.h" #include "atom.h" @@ -1660,6 +1661,9 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc) DRM_ERROR("Invalid crtc %u\n", pipe); return -EINVAL; } + if (!down_read_trylock(&adev->reset_domain->sem)) + return drm_crtc_vblank_count(crtc); + /* The hw increments its frame counter at start of vsync, not at start * of vblank, as is required by DRM core vblank counter handling. @@ -1705,6 +1709,7 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc) DRM_DEBUG_VBL("NULL mode info! Returned count may be wrong.\n"); } + up_read(&adev->reset_domain->sem); return count; } -- cgit v1.2.3 From 16c64495859f13f75602f439588883f2a4a4d6a6 Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Thu, 1 Oct 2026 21:13:07 +0500 Subject: drm/amdgpu: copy debugfs register data outside the GRBM/SRBM locks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reading or writing a register through the amdgpu_regs2 debugfs file with GRBM bank selection, which umr does for every banked access (a single "umr -b x x x -r " is enough, as is the wave dump with "-O halt_waves"), makes lockdep report a circular locking dependency, after which lockdep stays off until reboot: WARNING: possible circular locking dependency detected umr/63306 is trying to acquire lock: ffff8882cf4b8350 (&mm->mmap_lock){++++}-{4:4}, at: __might_fault+0x9b/0x140 but task is already holding lock: ffff8881aec010d0 (&amdgpu_grbm_lock_key){+.+.}-{4:4}, at: amdgpu_debugfs_regs2_op.isra.0+0x20d/0xd00 [amdgpu] ... Chain exists of: &mm->mmap_lock --> &amdgpu_srbm_lock_key --> &amdgpu_grbm_lock_key ... __might_fault+0xc5/0x140 amdgpu_debugfs_regs2_op.isra.0+0x441/0xd00 [amdgpu] full_proxy_write+0x10e/0x1d0 vfs_write+0x1d0/0xf50 ksys_write+0x116/0x250 amdgpu_debugfs_regs2_op() and amdgpu_debugfs_process_reg_op() call get_user()/put_user() for every dword while holding grbm_idx_mutex or srbm_mutex, and pm.mutex if requested, so a fault there takes mmap_lock inside those locks. The lock hierarchy in amdgpu_lockdep.c puts srbm_mutex and grbm_idx_mutex below notifier_lock, which amdgpu_cs_submit() takes with the BO reservations held, and dma_resv_lockdep() orders the reservations inside mmap_lock. So mmap_lock is outside grbm_idx_mutex and srbm_mutex, and these two debugfs paths nest it the other way round. The copies under the locks are as old as the bank selection in both files; commit 1d0f5838b126 ("drm/amdgpu: Add lockdep annotations for lock ordering validation") only made them visible to lockdep. Copy the user buffer in before any of the locks is taken and the result out after all of them are dropped, as amdgpu_debugfs_wave_read(), amdgpu_debugfs_gpr_read() and amdgpu_debugfs_gprwave_read() already do. The buffer is sized by the count passed to read() or write(), so an access larger than PAGE_SIZE is now rejected with -EINVAL; umr reads and writes one register at a time. A fault on the user buffer of a write now fails with -EFAULT before any register is written, instead of after a partial write. Fixes: 566281595037 ("drm/amd/amdgpu: Add bank selection for MMIO debugfs (v3)") Fixes: 37df9560cd3e ("drm/amd/amdgpu: New debugfs interface for MMIO registers (v5)") Acked-by: Christian König Acked-by: Tom St Denis Reviewed-by: Vitaly Prosyak Signed-off-by: Mikhail Gavrilov Signed-off-by: Alex Deucher (cherry picked from commit ccf9e35ed6811f2c25ba6368e7dc6481da17159d) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 77 +++++++++++++++++------------ 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index aca1a8045afa..ab7ccfaea22a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -121,6 +121,20 @@ static int amdgpu_debugfs_process_reg_op(bool read, struct file *f, *pos &= (1UL << 22) - 1; + if (size > PAGE_SIZE) + return -EINVAL; + + /* + * Access the user buffer only while none of the locks below are held: + * a fault takes mmap_lock, and grbm_idx_mutex and srbm_mutex nest + * inside it. + */ + u32 *data __free(kfree) = kmalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + if (!read && copy_from_user(data, buf, size)) + return -EFAULT; + r = pm_runtime_get_sync(adev_to_drm(adev)->dev); if (r < 0) { pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); @@ -152,28 +166,16 @@ static int amdgpu_debugfs_process_reg_op(bool read, struct file *f, mutex_lock(&adev->pm.mutex); while (size) { - uint32_t value; - - if (read) { - value = RREG32(*pos >> 2); - r = put_user(value, (uint32_t *)buf); - } else { - r = get_user(value, (uint32_t *)buf); - if (!r) - amdgpu_mm_wreg_mmio_rlc(adev, *pos >> 2, value, 0); - } - if (r) { - result = r; - goto end; - } + if (read) + data[result >> 2] = RREG32(*pos >> 2); + else + amdgpu_mm_wreg_mmio_rlc(adev, *pos >> 2, data[result >> 2], 0); result += 4; - buf += 4; *pos += 4; size -= 4; } -end: if (use_bank) { amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, 0); mutex_unlock(&adev->grbm_idx_mutex); @@ -188,6 +190,10 @@ end: pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); amdgpu_virt_disable_access_debugfs(adev); + + if (read && copy_to_user(buf, data, result)) + return -EFAULT; + return result; } @@ -238,11 +244,24 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f, char __user *buf, u32 off struct amdgpu_device *adev = rd->adev; ssize_t result = 0; int r; - uint32_t value; if (size & 0x3 || offset & 0x3) return -EINVAL; + if (size > PAGE_SIZE) + return -EINVAL; + + /* + * Access the user buffer only while none of the locks below are held: + * a fault takes mmap_lock, and grbm_idx_mutex and srbm_mutex nest + * inside it. + */ + u32 *data __free(kfree) = kmalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + if (write_en && copy_from_user(data, buf, size)) + return -EFAULT; + r = pm_runtime_get_sync(adev_to_drm(adev)->dev); if (r < 0) { pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); @@ -281,24 +300,16 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f, char __user *buf, u32 off mutex_lock(&adev->pm.mutex); while (size) { - if (!write_en) { - value = RREG32(offset >> 2); - r = put_user(value, (uint32_t *)buf); - } else { - r = get_user(value, (uint32_t *)buf); - if (!r) - amdgpu_mm_wreg_mmio_rlc(adev, offset >> 2, value, rd->id.xcc_id); - } - if (r) { - result = r; - goto end; - } + if (!write_en) + data[result >> 2] = RREG32(offset >> 2); + else + amdgpu_mm_wreg_mmio_rlc(adev, offset >> 2, data[result >> 2], + rd->id.xcc_id); offset += 4; size -= 4; result += 4; - buf += 4; } -end: + if (rd->id.use_grbm) { amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, rd->id.xcc_id); mutex_unlock(&adev->grbm_idx_mutex); @@ -317,6 +328,10 @@ end: pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); amdgpu_virt_disable_access_debugfs(adev); + + if (!write_en && copy_to_user(buf, data, result)) + return -EFAULT; + return result; } -- cgit v1.2.3 From f145d5b3f740de05d85257acdc82f6877180f5c6 Mon Sep 17 00:00:00 2001 From: Saleel Kudchadker Date: Thu, 24 Sep 2026 23:52:19 +0000 Subject: drm/amdkfd: align SDMA doorbell base for shader doorbell writes On GC 9.4.3, 9.4.4 and 9.5.0, doorbell writes issued from shaders are routed to an SDMA engine at 32-byte (4 doorbell index) granularity. SDMA engine doorbell ranges are 10 indices apart, so the range of every odd engine starts 2 indices into a 4-index block that begins in the previous engine's range. A shader ringing the doorbell of a queue on an odd engine therefore rings the previous engine instead. The intended queue never sees the doorbell, so its work stalls and the application hangs. Align the per-engine doorbell base up to a 4-index boundary on these ASICs. Each engine only uses 4 of its 10 indices, so the aligned base plus the queue offset stays within the engine's range. Even engines are already aligned, so only the doorbell IDs of odd-engine queues change. Only doorbells rung by the device itself are routed this way. Doorbell writes from the host, including the kernel's own SDMA rings, are not affected. The doorbell ID of an SDMA queue is saved in CRIU checkpoints and checked again on restore. A checkpoint taken before this change that contains an SDMA queue on an odd engine will fail to restore with -EINVAL. This is intentional: the restored process still holds the old doorbell address, so accepting the stale ID would bring back the same stall. Tested on MI300X with a shader-rung SDMA doorbell test: queues on odd engines hung before this change and complete with it. v2: use u32 instead of uint32_t for the new locals (checkpatch) v3: name the routing granularity, use dev->node_id, document the layout invariants, describe impact v4: express the granularity in doorbell indices, drop unrelated cleanups, explain the CRIU restore behavior, add test note Fixes: 6df442a03d1a ("drm/amdgpu: add new doorbell assignment table for aqua_vanjaram") Signed-off-by: Saleel Kudchadker Reviewed-by: Felix Kuehling Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit ee1379e6d7da47bdf7a8d5b89c2d1b28473701fb) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 32 ++++++++++++++++++---- drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 7 +++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 2f78395a0c31..4594824985e1 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -572,12 +572,32 @@ static int allocate_doorbell(struct qcm_process_device *qpd, * we need the physical sdma engine id in order to get the * correct doorbell offset. */ - uint32_t valid_id = idx_offset[qpd->dqm->dev->node_id * - get_num_all_sdma_engines(qpd->dqm) + - q->properties.sdma_engine_id] - + (q->properties.sdma_queue_id & 1) - * KFD_QUEUE_DOORBELL_MIRROR_OFFSET - + (q->properties.sdma_queue_id >> 1); + u32 engine_base = idx_offset[dev->node_id * + get_num_all_sdma_engines(qpd->dqm) + + q->properties.sdma_engine_id]; + u32 valid_id; + + /* + * With the aqua_vanjaram doorbell layout, a shader doorbell + * write whose 32-byte block starts in the previous engine's + * range rings that engine instead. Engine ranges are 10 + * indices apart (sdma_doorbell_range = 20 dwords, halved for + * 8-byte doorbells, in aqua_vanjaram_doorbell_index_init()), + * which puts odd engines 2 indices into a shared block. + * Aligning costs at most 2 indices, and 8 queues per engine use + * only 4 of the 10, so the aligned base stays in range. The + * 512-index mirror offset keeps the alignment. + */ + if (KFD_GC_VERSION(dev) == IP_VERSION(9, 4, 3) || + KFD_GC_VERSION(dev) == IP_VERSION(9, 4, 4) || + KFD_GC_VERSION(dev) == IP_VERSION(9, 5, 0)) + engine_base = ALIGN(engine_base, + KFD_SDMA_SHADER_DOORBELL_GRANULARITY); + + valid_id = engine_base + + (q->properties.sdma_queue_id & 1) * + KFD_QUEUE_DOORBELL_MIRROR_OFFSET + + (q->properties.sdma_queue_id >> 1); if (restore_id && *restore_id != valid_id) return -EINVAL; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index d8631847f0eb..435bf2692968 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -133,6 +133,13 @@ */ #define KFD_QUEUE_DOORBELL_MIRROR_OFFSET 512 +/* + * On GC 9.4.3, 9.4.4 and 9.5.0, doorbell writes issued from shaders are + * routed to an SDMA engine at 32-byte granularity, i.e. 4 indices with the + * 8-byte SDMA doorbells used on SOC15. + */ +#define KFD_SDMA_SHADER_DOORBELL_GRANULARITY 4 + /** * enum kfd_ioctl_flags - KFD ioctl flags * Various flags that can be set in &amdkfd_ioctl_desc.flags to control how -- cgit v1.2.3 From 54c998c55fc9cbc391b350d67b86e0c2a0748f1a Mon Sep 17 00:00:00 2001 From: Adrian Betschart Date: Fri, 25 Sep 2026 09:48:29 +0200 Subject: drm/amd/display: Don't replace a sink mode that shares the native totals decide_crtc_timing_for_drm_display_mode() copies the native mode's CRTC timing into the requested mode when clock, htotal and vtotal match. That is meant for the modes amdgpu inserts itself: amdgpu_dm_create_common_mode() duplicates the native mode and changes only hdisplay and vdisplay, so those are sent with the native timing and scaled. A sink can also offer a real mode with the same clock and totals as its native mode. CTA-861 VIC 102 (4096x2160@60) and VIC 97 (3840x2160@60) both use 594 MHz and 4400x2250. With 3840x2160@60 as the preferred mode, a 4096x2160@60 request goes out with the 3840x2160 timing and the framebuffer scaled into it (dst 3840x2025 at y=67), while the AVI InfoFrame still carries VIC 102. A JVC projector then reports 3840x2160 and shows a corrupted right edge. 4096x2160@24, whose totals differ, is sent and displayed correctly. Inserted modes keep the native sync positions; a real mode with its own active width does not. Compare those too, so only inserted modes are patched. Fixes: 4562236b3bc0 ("drm/amd/dc: Add dc display driver (v2)") Signed-off-by: Adrian Betschart Assisted-by: Claude-Code:claude-opus-5-5 Tested-by: Dan Wheeler Reviewed-by: Roman Li Signed-off-by: Alex Deucher (cherry picked from commit 22f9a2da101c37f821cd00954f81dc598754f4d5) Cc: stable@vger.kernel.org --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 7 +++- .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 42 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index d386bb1475bb..45a69bf43971 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1012,10 +1012,15 @@ decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, const struct drm_display_mode *native_mode, bool scale_enabled) { + /* Inserted modes copy the native sync; a sink mode with equal totals may not */ if (scale_enabled || ( native_mode->clock == drm_mode->clock && native_mode->htotal == drm_mode->htotal && - native_mode->vtotal == drm_mode->vtotal)) { + native_mode->vtotal == drm_mode->vtotal && + native_mode->hsync_start == drm_mode->hsync_start && + native_mode->hsync_end == drm_mode->hsync_end && + native_mode->vsync_start == drm_mode->vsync_start && + native_mode->vsync_end == drm_mode->vsync_end)) { if (native_mode->crtc_clock) copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode); } else { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 59ce682f491b..b61771749ca0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -1572,6 +1572,46 @@ static void dm_test_decide_crtc_timing_no_crtc_clock(struct kunit *test) KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 0); } +static void dm_test_decide_crtc_timing_sink_mode_same_totals(struct kunit *test) +{ + struct drm_display_mode native_mode = { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016, 4104, + 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }; + struct drm_display_mode drm_mode = { + DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 594000, 4096, 4184, 4272, + 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }; + + drm_mode_set_crtcinfo(&native_mode, 0); + drm_mode_set_crtcinfo(&drm_mode, 0); + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 4096); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hsync_start, 4184); +} + +static void dm_test_decide_crtc_timing_inserted_mode(struct kunit *test) +{ + struct drm_display_mode native_mode = { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016, 4104, + 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }; + struct drm_display_mode drm_mode; + + drm_mode_set_crtcinfo(&native_mode, 0); + drm_mode = native_mode; + drm_mode.hdisplay = 1920; + drm_mode.vdisplay = 1080; + drm_mode_set_crtcinfo(&drm_mode, 0); + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 3840); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_vdisplay, 2160); +} + /* Tests for amdgpu_dm_connector_funcs_reset() */ static const struct drm_connector_funcs dm_test_connector_funcs = { @@ -5635,6 +5675,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_decide_crtc_timing_matching_mode), KUNIT_CASE(dm_test_decide_crtc_timing_no_copy), KUNIT_CASE(dm_test_decide_crtc_timing_no_crtc_clock), + KUNIT_CASE(dm_test_decide_crtc_timing_sink_mode_same_totals), + KUNIT_CASE(dm_test_decide_crtc_timing_inserted_mode), /* amdgpu_dm_connector_funcs_reset */ KUNIT_CASE(dm_test_funcs_reset_sets_defaults), KUNIT_CASE(dm_test_funcs_reset_edp_abm_level), -- cgit v1.2.3 From e6895021a328594368a2a19406a53ffca1ce51c7 Mon Sep 17 00:00:00 2001 From: Haojie Li Date: Fri, 2 Oct 2026 10:29:27 +0800 Subject: drm/amdkfd: fix dma_buf reference leak in get_dmabuf_info When kzalloc fails for metadata_buffer in amdgpu_amdkfd_get_dmabuf_info(), the function returns -ENOMEM directly without calling dma_buf_put(), leaking the dma_buf reference obtained earlier via dma_buf_get(). Fix both issues by: - Adding an error check after the first amdgpu_bo_get_metadata() call - Replacing the direct return with r = -ENOMEM and goto out_put to ensure dma_buf_put() is called on all error paths Fixes: f54ce9e8cbd3 ("drm/amdkfd: Let driver decide buffer size at AMDKFD_IOC_GET_DMABUF_INFO ioctl") Signed-off-by: Haojie Li Signed-off-by: Alex Deucher (cherry picked from commit ffa699553332f8caf8688d312279ddcea9e3c141) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index 054870e9078d..ca5860e493a6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -598,14 +598,18 @@ int amdgpu_amdkfd_get_dmabuf_info(struct amdgpu_device *adev, int dma_buf_fd, /* first get metadata_size by buffer = NULL */ r = amdgpu_bo_get_metadata(bo, NULL, 0, metadata_size, NULL); + if (r) + goto out_put; /* user buf_size is bigger than bo metadata_size * allocate a buf at kernel space and copy */ if (*metadata_size <= buffer_size) { *metadata_buffer = kzalloc(*metadata_size, GFP_KERNEL); - if (!*metadata_buffer) - return -ENOMEM; + if (!*metadata_buffer) { + r = -ENOMEM; + goto out_put; + } r = amdgpu_bo_get_metadata(bo, *metadata_buffer, *metadata_size, NULL, &metadata_flags); -- cgit v1.2.3 From c7f44ec5c4ec760e74351f1843dcb8322f67cbf6 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 25 Sep 2026 09:16:26 -0400 Subject: drm/amdgpu: make Mac FB workaround generic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than adding a case of every chip where this is seen just check if the FB is at 0 and the ssid is apple. This should cover all chips which fall into this case. Tested-by: Ayhan Çiçek Signed-off-by: Alex Deucher (cherry picked from commit c54e4d10507722398dc90cf3c532e50b86cf425a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c index 3dc8faa091d7..61dc2a6be7e7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c @@ -1080,20 +1080,17 @@ void amdgpu_gmc_init_vga_resv_regions(struct amdgpu_device *adev) case CHIP_RENOIR: adev->mman.keep_stolen_vga_memory = true; break; - case CHIP_POLARIS10: - case CHIP_POLARIS11: - case CHIP_POLARIS12: - /* MacBookPros with switchable graphics put VRAM at 0 when - * the iGPU is enabled which results in cursor issues if - * the cursor ends up at 0. Reserve vram at 0 in that case. - */ - if (adev->gmc.vram_start == 0) - adev->mman.keep_stolen_vga_memory = true; - break; default: adev->mman.keep_stolen_vga_memory = false; break; } + /* MacBookPros with switchable graphics put VRAM at 0 when + * the iGPU is enabled which results in cursor issues if + * the cursor ends up at 0. Reserve vram at 0 in that case. + */ + if ((adev->pdev->subsystem_vendor == PCI_VENDOR_ID_APPLE) && + (adev->gmc.vram_start == 0)) + adev->mman.keep_stolen_vga_memory = true; if (amdgpu_sriov_vf(adev) || !amdgpu_device_has_display_hardware(adev)) { -- cgit v1.2.3 From 196d4d10c1b33ae176685ff42a9994a5952b1d0f Mon Sep 17 00:00:00 2001 From: Gyeyoung Baek Date: Thu, 1 Oct 2026 22:39:12 +0900 Subject: drm/imagination: Fix reference and vm_bo handling in remap() When a map overlaps part of an existing mapping, pvr_vm_gpuva_remap() splits the mapping into prev/next parts covering what the request did not take, instead of creating something new. It gets two things wrong. - A GEM reference is taken for each part but it's never needed and never dropped, so it leaks one reference per split (remap-next-in-2m in tests/imagination/pvr_vm_map.c): CRITICAL: 33558528 bytes of shmem still held after close - The parts still belong to the original object being split, but pvr_vm_gpuva_remap() links them to ctx->gpuvm_bo, the new object's vm_bo: prev_va --obj--> BO_A BO_B <--obj-- vm_bo (ctx->gpuvm_bo) \___________link___________/ (mismatch: BO_A != BO_B) drm_gpuva_link() catches the mismatch: WARNING: drivers/gpu/drm/drm_gpuvm.c:2108 at drm_gpuva_link+0x2ec/0x310 drm_WARN_ON(obj != vm_bo->obj) Call trace: drm_gpuva_link pvr_vm_gpuva_remap __drm_gpuvm_sm_map pvr_vm_map pvr_ioctl_vm_map Link them to op->remap.unmap->va->vm_bo instead. The locking of the GPUVA lists of the other objects touched by a split is not addressed here; it is handled by switching the GPUVM to immediate mode. Fixes: ff5f643de0bf ("drm/imagination: Add GEM and VM related code") Signed-off-by: Gyeyoung Baek Reviewed-by: Brajesh Gupta Link: https://patch.msgid.link/20261001-pvr-fixes-a-v2-1-f58254e5dfb8@gmail.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_vm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_vm.c b/drivers/gpu/drm/imagination/pvr_vm.c index 55cc999f3708..cbdd15ed74fd 100644 --- a/drivers/gpu/drm/imagination/pvr_vm.c +++ b/drivers/gpu/drm/imagination/pvr_vm.c @@ -418,6 +418,8 @@ pvr_vm_gpuva_unmap(struct drm_gpuva_op *op, void *op_ctx) static int pvr_vm_gpuva_remap(struct drm_gpuva_op *op, void *op_ctx) { + /* The split parts belong to the object of the mapping being split. */ + struct drm_gpuvm_bo *vm_bo = op->remap.unmap->va->vm_bo; struct pvr_vm_bind_op *ctx = op_ctx; u64 va_start = 0, va_range = 0; int err; @@ -433,14 +435,12 @@ pvr_vm_gpuva_remap(struct drm_gpuva_op *op, void *op_ctx) drm_gpuva_remap(&ctx->prev_va->base, &ctx->next_va->base, &op->remap); if (op->remap.prev) { - pvr_gem_object_get(gem_to_pvr_gem(ctx->prev_va->base.gem.obj)); - drm_gpuva_link(&ctx->prev_va->base, ctx->gpuvm_bo); + drm_gpuva_link(&ctx->prev_va->base, vm_bo); ctx->prev_va = NULL; } if (op->remap.next) { - pvr_gem_object_get(gem_to_pvr_gem(ctx->next_va->base.gem.obj)); - drm_gpuva_link(&ctx->next_va->base, ctx->gpuvm_bo); + drm_gpuva_link(&ctx->next_va->base, vm_bo); ctx->next_va = NULL; } -- cgit v1.2.3 From 789f290b2e81d42820bddcb4010ffbc2ae2ded54 Mon Sep 17 00:00:00 2001 From: Gyeyoung Baek Date: Thu, 1 Oct 2026 22:39:13 +0900 Subject: drm/imagination: Size page table preallocation by device address Commit 0a8224058a58 ("drm/imagination: Fix page count for page table for map() interface") passed the device address to pvr_mmu_op_context_create(), but the preallocation is still sized from device_addr + sgt_offset with an exclusive end. The offset into the object's pages has no place in a device-virtual range, and the exclusive end allocates one table too many when the range ends on a table boundary. Count the tables from device_addr and the inclusive end of the range. The new calculation does not take page table indices into account. This fixes another issue with the previous implementation, where the difference between start and end indices could underflow when a mapping crossed a page table boundary. This would lead to the driver preallocating a huge number of MMU pages, exhausting system memory and triggering an out of memory panic. Fixes: 0a8224058a58 ("drm/imagination: Fix page count for page table for map() interface") Fixes: ff5f643de0bf ("drm/imagination: Add GEM and VM related code") Signed-off-by: Gyeyoung Baek Reviewed-by: Brajesh Gupta Reviewed-by: Alessio Belle Cc: stable@vger.kernel.org [Alessio: add paragraph to point out OOM bug being fixed, cc stable] Link: https://patch.msgid.link/20261001-pvr-fixes-a-v2-2-f58254e5dfb8@gmail.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_mmu.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index 62eae7fcd5a2..67e73d7d5d0e 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -2337,7 +2337,7 @@ void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx) * @ctx: MMU context associated with owning VM context. * @sgt: Scatter gather table containing pages pinned for use by this context. * @device_addr: Virtual device address at the start of the requested mapping. - * @sgt_offset: Start offset of the requested device-virtual memory mapping. + * @sgt_offset: Offset into @sgt of the start of the requested mapping. * @size: Size in bytes of the requested device-virtual memory mapping. For an * unmapping, this should be zero so that no page tables are allocated. * @@ -2350,7 +2350,6 @@ struct pvr_mmu_op_context * pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, u64 device_addr, u64 sgt_offset, u64 size) { - u64 start_addr = device_addr + sgt_offset; int err; struct pvr_mmu_op_context *op_ctx = kzalloc_obj(*op_ctx); @@ -2365,18 +2364,18 @@ pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, if (size) { /* - * The number of page table objects we need to prealloc is - * indicated by the mapping size, start address and the sizes - * of the areas mapped per PT or PD. The range calculation is - * identical to that for the index into a table for a device - * address, so we reuse those functions here. + * The page tables needed are set by the device-virtual range + * being mapped: one level 1 table per 1GiB region and one + * level 0 table per 2MiB region the range touches. Tables that + * already exist are not consumed, so this is an upper bound. */ - const u32 l1_start_idx = pvr_page_table_l2_idx(start_addr); - const u32 l1_end_idx = pvr_page_table_l2_idx(start_addr + size); - const u32 l1_count = l1_end_idx - l1_start_idx + 1; - const u32 l0_start_idx = pvr_page_table_l1_idx(start_addr); - const u32 l0_end_idx = pvr_page_table_l1_idx(start_addr + size); - const u32 l0_count = l0_end_idx - l0_start_idx + 1; + const u64 last_addr = device_addr + size - 1; + const u64 l1_count = + (last_addr >> ROGUE_MMUCTRL_VADDR_PC_INDEX_SHIFT) - + (device_addr >> ROGUE_MMUCTRL_VADDR_PC_INDEX_SHIFT) + 1; + const u64 l0_count = + (last_addr >> ROGUE_MMUCTRL_VADDR_PD_INDEX_SHIFT) - + (device_addr >> ROGUE_MMUCTRL_VADDR_PD_INDEX_SHIFT) + 1; /* * Alloc and push page table entries until we have enough of -- cgit v1.2.3 From 6abc8e4dcfbc1d26a94c0ac037dfb3716f686b1f Mon Sep 17 00:00:00 2001 From: Maíra Canal Date: Fri, 25 Sep 2026 16:10:19 -0300 Subject: drm/vc4: Disable the V3D interrupt across runtime suspend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc4_irq_disable() masks the V3D interrupt sources and then calls synchronize_irq() before the V3D is powered down. However, by itself, this is not enough to quiesce the interrupt handler. synchronize_irq() waits for handlers that have already set IRQD_IRQ_INPROGRESS, and for irqchips reporting IRQCHIP_STATE_ACTIVE. A GIC interrupt chip is able to mark an interrupt active as soon as a CPU acknowledges it, so there these checks cover the whole dispatch path. On RPi 0-3, however, the interrupt controller is ARMCTRL, which has no active state. A CPU that has read the hwirq out of the pending register but has not yet reached handle_level_irq() stays invisible to synchronize_irq(). During a power transition, vc4_irq() may therefore run after the power domain is off, where every V3D register read will return 0xdeadbeef. 0xdeadbeef has FLDONE, FRDONE and OUTOMEM set. This problem doesn't trigger NULL pointer dereference issues only because the functions vc4_irq_finish_bin_job() and vc4_irq_finish_render_job() return early when there is no job pointer, but OUTOMEM is still able to schedule vc4_overflow_mem_work() after vc4_irq_disable() has already cancelled it. Therefore, fix the spurious interrupts by disabling the interrupt line across the PM transition. To preserve the enable/disable balance, move vc4_irq_install() and request the IRQ line disabled before the first runtime resume. Fixes: 9b6f461582e6 ("drm/vc4: v3d: Stop disabling interrupts") Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260925191318.541938-1-mcanal@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_irq.c | 18 +++--------------- drivers/gpu/drm/vc4/vc4_v3d.c | 13 +++++++------ 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 7877d493d80e..98a8db720091 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -276,7 +276,7 @@ vc4_irq_disable(struct drm_device *dev) V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS); /* Finish any interrupt handler still in flight. */ - synchronize_irq(vc4->irq); + disable_irq(vc4->irq); cancel_work_sync(&vc4->overflow_mem_work); } @@ -284,7 +284,6 @@ vc4_irq_disable(struct drm_device *dev) int vc4_irq_install(struct drm_device *dev, int irq) { struct vc4_dev *vc4 = to_vc4_dev(dev); - int ret; if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; @@ -298,19 +297,8 @@ int vc4_irq_install(struct drm_device *dev, int irq) init_waitqueue_head(&vc4->job_wait_queue); INIT_WORK(&vc4->overflow_mem_work, vc4_overflow_mem_work); - /* Clear any pending interrupts someone might have left around - * for us. - */ - V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS); - - ret = devm_request_irq(dev->dev, irq, vc4_irq, 0, - dev_name(dev->dev), dev); - if (ret) - return ret; - - vc4_irq_enable(dev); - - return 0; + return devm_request_irq(dev->dev, irq, vc4_irq, IRQF_NO_AUTOEN, + dev_name(dev->dev), dev); } void vc4_irq_uninstall(struct drm_device *dev) diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index 0f0c090afa6e..8ca0f4dde04b 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -397,6 +397,7 @@ static int vc4_v3d_runtime_resume(struct device *dev) vc4_v3d_init_hw(&vc4->base); vc4_irq_enable(&vc4->base); + enable_irq(vc4->irq); return 0; } @@ -453,6 +454,12 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) return ret; vc4->irq = ret; + ret = vc4_irq_install(drm, vc4->irq); + if (ret) { + drm_err(drm, "Failed to install IRQ handler\n"); + return ret; + } + ret = devm_pm_runtime_enable(dev); if (ret) return ret; @@ -474,12 +481,6 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) V3D_WRITE(V3D_BPOA, 0); V3D_WRITE(V3D_BPOS, 0); - ret = vc4_irq_install(drm, vc4->irq); - if (ret) { - drm_err(drm, "Failed to install IRQ handler\n"); - goto err_put_runtime_pm; - } - pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, 40); /* a little over 2 frames. */ pm_runtime_put_autosuspend(dev); -- cgit v1.2.3 From cca01232d94adcb10f7c8c5bb8ce072b045fcb58 Mon Sep 17 00:00:00 2001 From: Maíra Canal Date: Wed, 30 Sep 2026 15:20:44 -0300 Subject: drm/vc4: Fix binner slot allocation failing on an idle GPU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A job's binner slots are returned to bin_alloc_used by vc4_complete_exec(), which runs from the job_done workqueue, but the seqno vc4_v3d_get_bin_slot() waits on is incremented earlier, in the function vc4_irq_finish_render_job(). Therefore, a waiter can wake, retry, and still find the pool full because the worker has not run. By then, the job might have left the render_job_list, so no seqno remains to wait on and the allocation fails returning -ENOMEM. This scenario can be reproduced on a Raspberry Pi 3 by using a burst of small jobs (e.g. Piglit's `quick_gl` suite), with userspace seeing a rejected submit for a pool about to become free. Note that the slots are held from validation until the render completes, so the pool can also be exhausted by jobs still queued on bin_job_list, leaving render_job_list empty and nothing to wait on. Release the slots from the FRDONE handler and wait on the pool instead of on one job's seqno. This bounds the wait to the actual resource. Also, by claiming the slot inside the wait condition, another submit is unable to take the slot between the wakeup and the retry. vc4_complete_exec() keeps clearing the slots for jobs that never completed, and now wakes the waiters too. Note that the wait takes a deadline, since the two callers need different ones. The submit path is a user task and can be interrupted, so it waits without one. On the other hand, vc4_overflow_mem_work() runs on a worker and never sees a signal, so it needs a deadline. During a reset, vc4_irq_disable() masks V3D_DRIVER_IRQS before draining the worker, and with those masked nothing can release a slot. This deadlocks: the worker would wait for slots that only the reset can free, while the reset waits in cancel_work_sync() for the worker. Fixes: 553c942f8b2c ("drm/vc4: Allow using more than 256MB of CMA memory.") Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260930182204.1877361-1-mcanal@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_drv.h | 2 +- drivers/gpu/drm/vc4/vc4_gem.c | 8 ++++++- drivers/gpu/drm/vc4/vc4_irq.c | 6 +++++- drivers/gpu/drm/vc4/vc4_v3d.c | 44 +++++++++++++++++--------------------- drivers/gpu/drm/vc4/vc4_validate.c | 2 +- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 0f5958c1e6b6..b0e82c2dc1ba 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -1051,7 +1051,7 @@ void vc4_plane_async_set_fb(struct drm_plane *plane, /* vc4_v3d.c */ extern struct platform_driver vc4_v3d_driver; extern const struct of_device_id vc4_v3d_dt_match[]; -int vc4_v3d_get_bin_slot(struct vc4_dev *vc4); +int vc4_v3d_get_bin_slot(struct vc4_dev *vc4, long timeout); int vc4_v3d_bin_bo_get(struct vc4_dev *vc4, bool *used); void vc4_v3d_bin_bo_put(struct vc4_dev *vc4); int vc4_v3d_pm_get(struct vc4_dev *vc4); diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index e231c906709c..6ee4bd77d433 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -877,11 +877,17 @@ vc4_complete_exec(struct drm_device *dev, struct vc4_exec_info *exec) drm_gem_object_put(&bo->base.base); } - /* Free up the allocation of any bin slots we used. */ + /* Free up the allocation of any bin slots we used. Jobs that ran to + * completion had their slots released in vc4_irq_finish_render_job(). + * Only jobs that never completed still have slots to be released here. + */ spin_lock_irqsave(&vc4->job_lock, irqflags); vc4->bin_alloc_used &= ~exec->bin_slots; spin_unlock_irqrestore(&vc4->job_lock, irqflags); + /* Let anyone waiting on the binner pool retry. */ + wake_up_all(&vc4->job_wait_queue); + /* Release the reference on the binner BO if needed. */ if (exec->bin_bo_used) vc4_v3d_bin_bo_put(vc4); diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 98a8db720091..dc10d3a4d90f 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -74,7 +74,7 @@ vc4_overflow_mem_work(struct work_struct *work) bo = vc4->bin_bo; - bin_bo_slot = vc4_v3d_get_bin_slot(vc4); + bin_bo_slot = vc4_v3d_get_bin_slot(vc4, msecs_to_jiffies(500)); if (bin_bo_slot < 0) { drm_err(&vc4->base, "Couldn't allocate binner overflow mem\n"); goto complete; @@ -165,6 +165,10 @@ vc4_irq_finish_render_job(struct drm_device *dev) trace_vc4_rcl_end_irq(dev, exec->seqno); vc4->finished_seqno++; + + vc4->bin_alloc_used &= ~exec->bin_slots; + exec->bin_slots = 0; + list_move_tail(&exec->head, &vc4->job_done_list); nextbin = vc4_first_bin_job(vc4); diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index 8ca0f4dde04b..6844e8f3948c 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -173,46 +173,42 @@ static void vc4_v3d_init_hw(struct drm_device *dev) V3D_WRITE(V3D_VPMBASE, 0); } -int vc4_v3d_get_bin_slot(struct vc4_dev *vc4) +static int bin_slot_try_alloc(struct vc4_dev *vc4) { - struct drm_device *dev = &vc4->base; - unsigned long irqflags; int slot; - uint64_t seqno = 0; - struct vc4_exec_info *exec; - if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) - return -ENODEV; + guard(spinlock_irqsave)(&vc4->job_lock); -try_again: - spin_lock_irqsave(&vc4->job_lock, irqflags); slot = ffs(~vc4->bin_alloc_used); if (slot != 0) { /* Switch from ffs() bit index to a 0-based index. */ slot--; vc4->bin_alloc_used |= BIT(slot); - spin_unlock_irqrestore(&vc4->job_lock, irqflags); - return slot; + } else { + slot = -ENOMEM; } - /* Couldn't find an open slot. Wait for render to complete - * and try again. - */ - exec = vc4_last_render_job(vc4); - if (exec) - seqno = exec->seqno; - spin_unlock_irqrestore(&vc4->job_lock, irqflags); + return slot; +} - if (seqno) { - int ret = vc4_wait_for_seqno(dev, seqno, ~0ull, true); +int vc4_v3d_get_bin_slot(struct vc4_dev *vc4, long timeout) +{ + int slot; + long ret; - if (ret == 0) - goto try_again; + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) + return -ENODEV; + /* If the pool is full, wait for a job to release its slots. */ + ret = wait_event_interruptible_timeout(vc4->job_wait_queue, + (slot = bin_slot_try_alloc(vc4)) >= 0, + timeout); + if (ret < 0) return ret; - } + if (ret == 0) + return -ENOMEM; - return -ENOMEM; + return slot; } /* diff --git a/drivers/gpu/drm/vc4/vc4_validate.c b/drivers/gpu/drm/vc4/vc4_validate.c index d2a65c968b1f..565227657b43 100644 --- a/drivers/gpu/drm/vc4/vc4_validate.c +++ b/drivers/gpu/drm/vc4/vc4_validate.c @@ -402,7 +402,7 @@ validate_tile_binning_config(VALIDATE_ARGS) return -EINVAL; } - bin_slot = vc4_v3d_get_bin_slot(vc4); + bin_slot = vc4_v3d_get_bin_slot(vc4, MAX_SCHEDULE_TIMEOUT); if (bin_slot < 0) { if (bin_slot != -EINTR && bin_slot != -ERESTARTSYS) { drm_err(dev, "Failed to allocate binner memory: %d\n", -- cgit v1.2.3 From 6ccf996a0dec1852dab94ad865f27b4904750e12 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Wed, 7 Oct 2026 16:30:15 +0200 Subject: drm/nouveau: Skip the Turing CE workaround object for non-GR channels This workaround is here for the old Gallium driver and predate async CE and NVDEC. Instead of only skipping it for NVDEC, let's only apply it in case of GR channels. This should make async CE work properly on Turing by stopping the assign of a GRCE. (here CE0) Signed-off-by: Mary Guillemard Reviewed-by: Lyude Paul Reviewed-by: Daniel Almeida Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20261007-turing-async-ce-fix-v1-1-12ee6ccd1a3c@mary.zone --- drivers/gpu/drm/nouveau/nouveau_abi16.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_abi16.c b/drivers/gpu/drm/nouveau/nouveau_abi16.c index 4542d5f4ded8..b776030460f7 100644 --- a/drivers/gpu/drm/nouveau/nouveau_abi16.c +++ b/drivers/gpu/drm/nouveau/nouveau_abi16.c @@ -488,7 +488,7 @@ nouveau_abi16_ioctl_channel_alloc(ABI16_IOCTL_ARGS) goto done; break; case NV_DEVICE_INFO_V0_TURING: - if (engine != NV_DEVICE_HOST_RUNLIST_ENGINES_NVDEC) { + if (engine == NV_DEVICE_HOST_RUNLIST_ENGINES_GR) { ret = nvif_object_ctor(&chan->chan->user, "abi16CeWar", 0, TURING_DMA_COPY_A, NULL, 0, &chan->ce); -- cgit v1.2.3