From 94579f24e2b526a04eb41050af0ba018c6f528e7 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Aug 2026 10:08:09 +0300 Subject: [PATCH 01/53] drm/virtio: Fix a NULL vs ERR_PTR() bug in virtio_gpu_user_framebuffer_create() Smatch complains that returning a NULL here will lead to a NULL pointer dereference in drm_mode_addfb2(). Return an error pointer instead. Fixes: dc5698e80cf7 ("Add virtio gpu driver.") Signed-off-by: Dan Carpenter Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/an1tWfHIHwtXd9SO@stanley.mountain --- drivers/gpu/drm/virtio/virtgpu_display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_display.c b/drivers/gpu/drm/virtio/virtgpu_display.c index 44ffffec550f..85ea252c658e 100644 --- a/drivers/gpu/drm/virtio/virtgpu_display.c +++ b/drivers/gpu/drm/virtio/virtgpu_display.c @@ -344,7 +344,7 @@ virtio_gpu_user_framebuffer_create(struct drm_device *dev, if (ret) { kfree(virtio_gpu_fb); drm_gem_object_put(obj); - return NULL; + return ERR_PTR(ret); } return &virtio_gpu_fb->base; From d96504ea631874220d89c455d735da51a796ead0 Mon Sep 17 00:00:00 2001 From: shechenglong Date: Tue, 11 Aug 2026 09:56:24 +0800 Subject: [PATCH 02/53] drm/virtio: check return value of vgdev_output_init() The return value of vgdev_output_init(), called by virtio_gpu_modeset_init(), is not checked. As a result, modeset initialization continues even if an output fails to initialize. check the return value and return the error to the caller. Signed-off-by: shechenglong Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260811015624.830-1-shechenglong@xfusion.com --- drivers/gpu/drm/virtio/virtgpu_display.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_display.c b/drivers/gpu/drm/virtio/virtgpu_display.c index 85ea252c658e..a1a875a0c706 100644 --- a/drivers/gpu/drm/virtio/virtgpu_display.c +++ b/drivers/gpu/drm/virtio/virtgpu_display.c @@ -378,8 +378,11 @@ int virtio_gpu_modeset_init(struct virtio_gpu_device *vgdev) vgdev->ddev->mode_config.fb_modifiers_not_supported = true; - for (i = 0 ; i < vgdev->num_scanouts; ++i) - vgdev_output_init(vgdev, i); + for (i = 0; i < vgdev->num_scanouts; ++i) { + ret = vgdev_output_init(vgdev, i); + if (ret) + return ret; + } ret = drm_vblank_init(vgdev->ddev, vgdev->num_scanouts); if (ret) From 61d85f99b5a55d4f717c4bb2f4c4acabf22ee3fc Mon Sep 17 00:00:00 2001 From: Anuj Bolewar Date: Sun, 2 Aug 2026 22:05:17 +0530 Subject: [PATCH 03/53] drm/virtio: reclaim pending vbufs before tearing down vqs virtio_gpu_free_vbufs() destroys the vbufs kmem_cache after the virtqueues have already been released. Commands that were queued but never completed by the device leave their vbuffers stranded in the virtqueue, so the cache still holds live objects when virtio_gpu_deinit() tears everything down. This triggers a WARNING in virtio_gpu_free_vbufs: BUG virtio-gpu-vbufs (Not tainted): Objects remaining in cache on __kmem_cache_shutdown() Drain any buffers still sitting in the control and cursor virtqueues in virtio_gpu_deinit() after the device has been reset and before the virtqueues are deleted, following the same pattern used by virtio_console's remove_vqs(). Each reclaimed buffer is released with free_vbuf(), dropping the reference on any GEM objects it holds. Pending RESOURCE_UNREF commands are handled as well: their resp_cb_data still references a GEM object, so it is cleaned up with virtio_gpu_cleanup_object() to avoid leaking it on teardown. Reported-by: syzbot+06f9b2a53ba4a5a47644@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=06f9b2a53ba4a5a47644 Signed-off-by: Anuj Bolewar Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260802-virtio-gpu-reclaim-vbufs-v2-1-5767fb860691@gmail.com --- drivers/gpu/drm/virtio/virtgpu_drv.h | 1 + drivers/gpu/drm/virtio/virtgpu_kms.c | 1 + drivers/gpu/drm/virtio/virtgpu_vq.c | 15 +++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 7449907754a4..3e491c808734 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -332,6 +332,7 @@ void virtio_gpu_array_put_free_work(struct work_struct *work); /* virtgpu_vq.c */ int virtio_gpu_alloc_vbufs(struct virtio_gpu_device *vgdev); void virtio_gpu_free_vbufs(struct virtio_gpu_device *vgdev); +void virtio_gpu_reclaim_vbufs(struct virtio_gpu_device *vgdev); void virtio_gpu_cmd_create_resource(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo, struct virtio_gpu_object_params *params, diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c index b4329f28e976..e5a6ae679f36 100644 --- a/drivers/gpu/drm/virtio/virtgpu_kms.c +++ b/drivers/gpu/drm/virtio/virtgpu_kms.c @@ -298,6 +298,7 @@ void virtio_gpu_deinit(struct drm_device *dev) flush_work(&vgdev->cursorq.dequeue_work); flush_work(&vgdev->config_changed_work); virtio_reset_device(vgdev->vdev); + virtio_gpu_reclaim_vbufs(vgdev); vgdev->vdev->config->del_vqs(vgdev->vdev); } diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index e5e1af8b8e8a..ab6106f4bdfc 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -208,6 +208,21 @@ static void free_vbuf(struct virtio_gpu_device *vgdev, kmem_cache_free(vgdev->vbufs, vbuf); } +void virtio_gpu_reclaim_vbufs(struct virtio_gpu_device *vgdev) +{ + struct virtio_gpu_vbuffer *vbuf; + + while ((vbuf = virtqueue_detach_unused_buf(vgdev->ctrlq.vq))) { + if (vbuf->objs) + virtio_gpu_array_put_free(vbuf->objs); + if (vbuf->resp_cb_data) + virtio_gpu_cleanup_object(vbuf->resp_cb_data); + free_vbuf(vgdev, vbuf); + } + while ((vbuf = virtqueue_detach_unused_buf(vgdev->cursorq.vq))) + free_vbuf(vgdev, vbuf); +} + static void reclaim_vbufs(struct virtqueue *vq, struct list_head *reclaim_list) { struct virtio_gpu_vbuffer *vbuf; From 6a736d2f9d0c6e6217fe7532bc4c50ceca71db78 Mon Sep 17 00:00:00 2001 From: Benjamin Leggett Date: Thu, 6 Aug 2026 18:54:22 -0400 Subject: [PATCH 04/53] drm/virtio: use the DMA API for resource backing on Xen On a Xen PV domain page addresses bear no relation to the real machine addresses the host would have to use to reach it. virtio_ring.c handles this correctly, vring_use_map_api() returns true for any xen_domain() regardless of VIRTIO_F_ACCESS_PLATFORM. virtio-gpu makes the same decision independently, but its copy looks only at the feature bit: bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev); QEMU does not set iommu_platform on virtio-vga by default, so VIRTIO_F_ACCESS_PLATFORM is not negotiated, use_dma_api is false, and virtio_gpu_object_shmem_init() describes the framebuffer's backing pages to the host with sg_phys(). Those are guest-physical addresses. In a PV domain they resolve, on the host side, to pages belonging to some other domain, so the host scans out unrelated memory. Move the decision into virtio_gpu_use_dma_api() and give it the xen_domain() check, like vring_use_map_api() has. This additionally enables the dma_sync_sgtable_for_device() calls in virtgpu_vq.c, which are required for correctness whenever swiotlb is in play. Reproduced with a Xen 4.21 PV dom0 nested inside QEMU 8.2 with virtio-vga, on both a distro 6.8 kernel and 6.18 LTS. A PVH dom0 works fine and doesn't need this fix because it is identity-mapped, only PV dom0s are affected. Fixes: a3b815f09bb8 ("drm/virtio: add iommu support.") Signed-off-by: Ben Leggett Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260806-virtgpu-xen-dma-v1-1-e499b345bbad@edera.io --- drivers/gpu/drm/virtio/virtgpu_drv.h | 20 ++++++++++++++++++++ drivers/gpu/drm/virtio/virtgpu_object.c | 2 +- drivers/gpu/drm/virtio/virtgpu_vq.c | 6 +++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 3e491c808734..626aadf680bd 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -43,6 +43,8 @@ #include #include +#include + #define DRIVER_NAME "virtio_gpu" #define DRIVER_DESC "virtio GPU" @@ -60,6 +62,24 @@ /* See virtio_gpu_ctx_create. One additional character for NULL terminator. */ #define DEBUG_NAME_MAX_LEN 65 +/* + * Whether the host must be told about resource backing pages by DMA address + * rather than guest-physical address. + * + * This mirrors vring_use_map_api() in drivers/virtio/virtio_ring.c, including + * its xen_domain() case. + */ +static inline bool virtio_gpu_use_dma_api(const struct virtio_device *vdev) +{ + if (!virtio_has_dma_quirk(vdev)) + return true; + + if (xen_domain()) + return true; + + return false; +} + struct virtio_gpu_object_params { unsigned long size; bool dumb; diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/drm/virtio/virtgpu_object.c index ec9efacc6919..1527c62be88b 100644 --- a/drivers/gpu/drm/virtio/virtgpu_object.c +++ b/drivers/gpu/drm/virtio/virtgpu_object.c @@ -163,7 +163,7 @@ static int virtio_gpu_object_shmem_init(struct virtio_gpu_device *vgdev, struct virtio_gpu_mem_entry **ents, unsigned int *nents) { - bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev); + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); struct scatterlist *sg; struct sg_table *pages; int si; diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index ab6106f4bdfc..5e9b7b192db0 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -739,7 +739,7 @@ int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]); struct virtio_gpu_transfer_to_host_2d *cmd_p; struct virtio_gpu_vbuffer *vbuf; - bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev); + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); if (virtio_gpu_is_shmem(bo) && use_dma_api) dma_sync_sgtable_for_device(vgdev->vdev->dev.parent, @@ -770,7 +770,7 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]); struct virtio_gpu_transfer_to_host_2d *cmd_p; struct virtio_gpu_vbuffer *vbuf; - bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev); + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); if (virtio_gpu_is_shmem(bo) && use_dma_api) dma_sync_sgtable_for_device(vgdev->vdev->dev.parent, @@ -1203,7 +1203,7 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]); struct virtio_gpu_transfer_host_3d *cmd_p; struct virtio_gpu_vbuffer *vbuf; - bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev); + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); if (virtio_gpu_is_shmem(bo) && use_dma_api) dma_sync_sgtable_for_device(vgdev->vdev->dev.parent, From ab243f74ab4084ca5c8dec608cb5b0deb27db067 Mon Sep 17 00:00:00 2001 From: Youssef Samir Date: Fri, 31 Jul 2026 17:23:44 +0200 Subject: [PATCH 05/53] accel/qaic: Address potential out-of-bounds read in resp_worker() Although 'commit 2feec5ae5df7 ("accel/qaic: Handle DBC deactivation if the owner went away")' fixes the scenario it was intended for by walking the message and only decoding QAIC_TRANS_DEACTIVATE_FROM_DEV, if present, it skipped over the bounds checking code that is included in decode_message(). This could lead to issues such as reading past the slab allocation's end, infinite loops or kernel panics. For those issues to happen, a malformed wire message is needed to be sent from the device. Instead of duplicating the bounds checking code already present in decode_message(), use the function inside resp_worker(). Reported-by: Ruikai Peng Fixes: 2feec5ae5df7 ("accel/qaic: Handle DBC deactivation if the owner went away") Reviewed-by: Jeff Hugo Reviewed-by: Lizhi Hou Signed-off-by: Youssef Samir Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20260731152344.1905882-1-youssef.abdulrahman@oss.qualcomm.com --- drivers/accel/qaic/qaic_control.c | 46 ++++++++++++++++--------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/drivers/accel/qaic/qaic_control.c b/drivers/accel/qaic/qaic_control.c index 50bf3340e49c..2ccc55486aac 100644 --- a/drivers/accel/qaic/qaic_control.c +++ b/drivers/accel/qaic/qaic_control.c @@ -963,11 +963,13 @@ static int decode_status(struct qaic_device *qdev, void *trans, struct manage_ms static int decode_message(struct qaic_device *qdev, struct manage_msg *user_msg, struct wire_msg *msg, struct ioctl_resources *resources, - struct qaic_user *usr) + struct qaic_user *usr, bool orphaned_deactivate) { + u32 msg_hdr_count = le32_to_cpu(msg->hdr.count); u32 msg_hdr_len = le32_to_cpu(msg->hdr.len); struct wire_trans_hdr *trans_hdr; u32 msg_len = 0; + int trans_type; int ret; int i; @@ -975,10 +977,12 @@ static int decode_message(struct qaic_device *qdev, struct manage_msg *user_msg, msg_hdr_len > QAIC_MANAGE_MAX_MSG_LENGTH) return -EINVAL; - user_msg->len = 0; - user_msg->count = le32_to_cpu(msg->hdr.count); + if (user_msg) { + user_msg->len = 0; + user_msg->count = msg_hdr_count; + } - for (i = 0; i < user_msg->count; ++i) { + for (i = 0; i < msg_hdr_count; ++i) { u32 hdr_len; if (msg_len > msg_hdr_len - sizeof(*trans_hdr)) @@ -990,7 +994,20 @@ static int decode_message(struct qaic_device *qdev, struct manage_msg *user_msg, size_add(msg_len, hdr_len) > msg_hdr_len) return -EINVAL; - switch (le32_to_cpu(trans_hdr->type)) { + trans_type = le32_to_cpu(trans_hdr->type); + /* + * orphaned_deactivate is the case where a deactivate response + * is received from the device after the user owning the DBC, + * and the message requesting deactivation, has gone away. + * In this case, only process QAIC_TRANS_DEACTIVATE_FROM_DEV + * transaction and skip the others. + */ + if (orphaned_deactivate && trans_type != QAIC_TRANS_DEACTIVATE_FROM_DEV) { + msg_len += hdr_len; + continue; + } + + switch (trans_type) { case QAIC_TRANS_PASSTHROUGH_FROM_DEV: ret = decode_passthrough(qdev, trans_hdr, user_msg, &msg_len); break; @@ -1281,7 +1298,7 @@ static int qaic_manage(struct qaic_device *qdev, struct qaic_user *usr, struct m goto dma_cont_failed; } - ret = decode_message(qdev, user_msg, rsp, &resources, usr); + ret = decode_message(qdev, user_msg, rsp, &resources, usr, false); dma_cont_failed: free_dbc_buf(qdev, &resources); @@ -1446,22 +1463,7 @@ static void resp_worker(struct work_struct *work) * response to the QAIC_TRANS_TERMINATE_TO_DEV transaction, * otherwise, the user can issue an soc_reset to the device. */ - u32 msg_count = le32_to_cpu(msg->hdr.count); - u32 msg_len = le32_to_cpu(msg->hdr.len); - u32 len = 0; - int j; - - for (j = 0; j < msg_count && len < msg_len; ++j) { - struct wire_trans_hdr *trans_hdr; - - trans_hdr = (struct wire_trans_hdr *)(msg->data + len); - if (le32_to_cpu(trans_hdr->type) == QAIC_TRANS_DEACTIVATE_FROM_DEV) { - if (decode_deactivate(qdev, trans_hdr, &len, NULL)) - len += le32_to_cpu(trans_hdr->len); - } else { - len += le32_to_cpu(trans_hdr->len); - } - } + decode_message(qdev, NULL, msg, NULL, NULL, true); /* request must have timed out, drop packet */ kfree(msg); } From dc14753664240cedf669623b27ae9922b0618b25 Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Tue, 18 Aug 2026 02:06:55 +0300 Subject: [PATCH 06/53] accel/amdxdna: return early from a zero-length flush SYNC_BO does not constrain its size, so a request for zero bytes reaches drm_clflush_virt_range(), which ends with an unconditional clflushopt(end - 1). For an empty range that is the byte before the mapping, and abo->mem.kva comes from vmap(), so the access lands in the guard page below the vmalloc area and faults: BUG: unable to handle page fault for address: ffffd16fbbc70fff #PF: supervisor read access in kernel mode Oops: Oops: 0000 [#1] SMP NOPTI CPU: 7 UID: 1000 Comm: sync_bo_probe RIP: 0010:drm_clflush_virt_range+0x3c/0x70 Call Trace: amdxdna_drm_sync_bo_ioctl+0x124/0x430 [amdxdna] drm_ioctl+0x301/0x4c0 __x64_sys_ioctl+0x115/0x2f0 do_syscall_64+0xa6/0x3d0 Any process that can open the render node can do this. Reproduced 3 of 3 times on a Strix Point NPU (1022:17f0), by calling SYNC_BO with size 0 on an AMDXDNA_BO_SHARE object. The import arm takes the same request but flushes the whole scatterlist, so it survives it. Nothing needs flushing for an empty range, so answer before choosing a path. Fixes: e252e3f3488a ("accel/amdxdna: Revise device bo creation and free") Cc: stable@vger.kernel.org Signed-off-by: Taimuraz Kaitmazov Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260817230655.356785-1-taimuraz@kaitmazov.com --- drivers/accel/amdxdna/amdxdna_gem.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 1c63eff0a4a8..2a16de96e6a4 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -1247,6 +1247,9 @@ static int amdxdna_flush_bo(struct amdxdna_gem_obj *abo, u64 offset, u64 size) return -EINVAL; size = min(abo->mem.size, end) - offset; + if (!size) + return 0; + if (is_import_bo(abo)) drm_clflush_sg(abo->base.sgt); else if (amdxdna_gem_vmap(abo)) From 511585987d27d8cb668acebd399fc4deda23404c Mon Sep 17 00:00:00 2001 From: Marek Czernohous Date: Thu, 13 Aug 2026 01:13:27 +0200 Subject: [PATCH 07/53] drm/nouveau: unsubscribe the channel-kill event before the fence context nouveau_channel_del() tears the fence context down first and only drops the channel-kill subscription later, in the middle of the nvif object teardown: if (chan->fence) nouveau_fence(chan->cli->drm)->context_del(chan); ... nvif_object_dtor(&chan->vram); nvif_event_dtor(&chan->kill); The subscribed handler is nouveau_channel_killed(), which calls nouveau_channel_kill() and from there nouveau_fence_context_kill() on chan->fence. A kill event delivered in that window takes fctx->lock and walks fctx->pending on a fence context that context_del() has already freed. Nothing reaches this below Fermi today, because the subscription is gated on FERMI_CHANNEL_GPFIFO and nothing kills a channel there. On Fermi and newer the window is real but narrow, since a kill has to land exactly while the channel is being destroyed. That is reason enough on its own, which is why this carries a Fixes: tag. The last patch in this series subscribes Tesla channels as well; nothing kills those today, so it does not widen the exposure now, but it is the groundwork for a recovery path that would, and the ordering is better fixed before that lands than alongside it. Drop the subscription before anything it depends on is torn down. Fixes: ea13e5abf807 ("drm/nouveau: signal pending fences when channel has been killed") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Marek Czernohous Fixes: ea13e5abf807 ("drm/nouveau: signal pending fences when channel has been killed") Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260812231330.705425-2-mczernohous@gmail.com --- drivers/gpu/drm/nouveau/nouveau_chan.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.c b/drivers/gpu/drm/nouveau/nouveau_chan.c index 598513f60449..f142f6310596 100644 --- a/drivers/gpu/drm/nouveau/nouveau_chan.c +++ b/drivers/gpu/drm/nouveau/nouveau_chan.c @@ -90,6 +90,14 @@ nouveau_channel_del(struct nouveau_channel **pchan) { struct nouveau_channel *chan = *pchan; if (chan) { + /* + * Drop the kill-event subscription first. Its handler + * dereferences chan->fence, which the fence context teardown + * below frees, so leaving it armed across the teardown leaves + * a window for a use-after-free. + */ + nvif_event_dtor(&chan->kill); + if (chan->fence) nouveau_fence(chan->cli->drm)->context_del(chan); @@ -100,7 +108,6 @@ nouveau_channel_del(struct nouveau_channel **pchan) nvif_object_dtor(&chan->nvsw); nvif_object_dtor(&chan->gart); nvif_object_dtor(&chan->vram); - nvif_event_dtor(&chan->kill); nvif_object_dtor(&chan->user); nvif_mem_dtor(&chan->mem_userd); nouveau_vma_del(&chan->sema.vma); From 500cb24cd61bad8a2747ddfc49b7034899c82d94 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Sun, 16 Aug 2026 14:22:34 +0530 Subject: [PATCH 08/53] drm/gud: NUL-terminate TV mode names read from the device gud_connector_add_tv_mode() reads a buffer of fixed-size mode names from the USB device and passes pointers into it to drm_mode_create_tv_properties_legacy(), which calls strlen() on each one. Nothing guarantees the device NUL-terminates a name, so strlen() can run past the end of a slot and, for the last mode, past the end of the allocation. Terminate each name at the end of its slot before use. Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Reported-by: syzbot+916c888ba5f1a54c9526@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=916c888ba5f1a54c9526 Tested-by: syzbot+916c888ba5f1a54c9526@syzkaller.appspotmail.com Signed-off-by: Deepanshu Kartikey Acked-by: Ruben Wauters Cc: Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260816085234.22053-1-kartikey406@gmail.com --- drivers/gpu/drm/gud/gud_connector.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/gud/gud_connector.c b/drivers/gpu/drm/gud/gud_connector.c index ea0cca58b7c8..5c0065c876a7 100644 --- a/drivers/gpu/drm/gud/gud_connector.c +++ b/drivers/gpu/drm/gud/gud_connector.c @@ -396,8 +396,13 @@ static int gud_connector_add_tv_mode(struct gud_device *gdrm, struct drm_connect } num_modes = ret / GUD_CONNECTOR_TV_MODE_NAME_LEN; - for (i = 0; i < num_modes; i++) - modes[i] = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN]; + for (i = 0; i < num_modes; i++) { + char *mode = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN]; + + /* The device is not trusted to NUL-terminate the name */ + mode[GUD_CONNECTOR_TV_MODE_NAME_LEN - 1] = '\0'; + modes[i] = mode; + } ret = drm_mode_create_tv_properties_legacy(connector->dev, num_modes, modes); free: From da1ea35fea67ad841f4ada28dd61b41be65e5437 Mon Sep 17 00:00:00 2001 From: Tao Yu Date: Wed, 19 Aug 2026 15:28:35 +0800 Subject: [PATCH 09/53] drm/gud: validate TV mode names before creating enum property The GUD protocol returns TV mode names as fixed-size GUD_CONNECTOR_TV_MODE_NAME_LEN entries and requires each name to be NUL-terminated. gud_connector_add_tv_mode() currently passes each fixed-size entry directly to drm_mode_create_tv_properties_legacy(), which eventually reaches drm_property_add_enum() and strlen(). If a device returns an entry without a terminating NUL byte, strlen() reads past the end of the slot and can run beyond the allocated buffer, triggering an out-of-bounds read. Validate that each returned TV mode name contains a NUL terminator within its fixed-size slot before passing it to the DRM property code. If a malformed entry is found, reject the device response with -EIO. This fixes the out-of-bounds read without changing the handling of valid devices, and avoids silently truncating malformed protocol data. Reported-by: syzbot+9ae8e7884e451eaed5b4@syzkaller.appspotmail.com Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Signed-off-by: Tao Yu Reviewed-by: Ruben Wauters Cc: Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260819072835.4074130-1-tao1.yu@intel.com --- drivers/gpu/drm/gud/gud_connector.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/gud/gud_connector.c b/drivers/gpu/drm/gud/gud_connector.c index 5c0065c876a7..8141c3a1e30a 100644 --- a/drivers/gpu/drm/gud/gud_connector.c +++ b/drivers/gpu/drm/gud/gud_connector.c @@ -399,8 +399,11 @@ static int gud_connector_add_tv_mode(struct gud_device *gdrm, struct drm_connect for (i = 0; i < num_modes; i++) { char *mode = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN]; - /* The device is not trusted to NUL-terminate the name */ - mode[GUD_CONNECTOR_TV_MODE_NAME_LEN - 1] = '\0'; + if (!memchr(mode, '\0', GUD_CONNECTOR_TV_MODE_NAME_LEN)) { + ret = -EIO; + goto free; + } + modes[i] = mode; } From cb732d027aa18e1fcf9d2797f47d20b179ebc59c Mon Sep 17 00:00:00 2001 From: Sajal Gupta Date: Fri, 21 Aug 2026 12:46:13 +0530 Subject: [PATCH 10/53] drm/gud: validate GUD_ROTATION_0 is present in supported rotations The rotation argument to drm_plane_create_rotation_property() is set to DRM_MODE_ROTATE_0, and the device reported rotation bitmask is used as the supported_rotations argument. The driver never validates that GUD_ROTATION_0 is present, so a device that omits it from its GUD_PROPERTY_ROTATION triggers the WARN_ON(rotation & ~supported_rotations) in drm_plane_create_rotation_property() Fix this by skipping the creation of rotation property if the device doesn't have the GUD_ROTATION_0 bit Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Reported-by: syzbot+efe2810681f1b065d3a8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=efe2810681f1b065d3a8 Tested-by: syzbot+efe2810681f1b065d3a8@syzkaller.appspotmail.com Signed-off-by: Sajal Gupta Acked-by: Ruben Wauters Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260821071812.16500-1-sajal2005gupta@gmail.com --- drivers/gpu/drm/gud/gud_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/gud/gud_drv.c b/drivers/gpu/drm/gud/gud_drv.c index 89bd6ca36003..3a1b9e2a2eaa 100644 --- a/drivers/gpu/drm/gud/gud_drv.c +++ b/drivers/gpu/drm/gud/gud_drv.c @@ -289,6 +289,8 @@ static int gud_plane_add_properties(struct gud_device *gdrm) * but mask out any additions on future devices. */ val &= GUD_ROTATION_MASK; + if (!(val & GUD_ROTATION_0)) + continue; ret = drm_plane_create_rotation_property(&gdrm->plane, DRM_MODE_ROTATE_0, val); break; From 30d0aff2c65a277135cfd8ea28fa1ee75e0ea4e0 Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Mon, 17 Aug 2026 13:04:54 +0800 Subject: [PATCH 11/53] dma-buf: dma-heap: don't publish fd before copy_to_user() succeeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DMA_HEAP_IOCTL_ALLOC allocates a dma-buf and installs an fd into the caller's fd table via dma_buf_fd() -> fd_install() before dma_heap_ioctl() copies the result back to userspace. If the trailing copy_to_user() fails, userspace never learns the fd number, but the fd (and the underlying dma-buf reference) are already visible to other threads in the same process and are leaked for the lifetime of the process. The obvious "close it on the failure path" fix is unsafe: once fd_install() has run, another thread can already dup() the fd, send it via SCM_RIGHTS, or close() it and let its number be reused, so a subsequent close_fd() from the ioctl path can operate on an unrelated file. This was pointed out by Christian König on v1 [1]. Restructure the allocation path so that fd_install() is the last, unfailable step of a successful ioctl: 1. heap->ops->allocate() creates the dma_buf. 2. get_unused_fd_flags() reserves an fd number in the caller's fd table without publishing it, so no other thread can observe it. 3. copy_to_user() delivers the fd number to userspace; on failure the fd is returned with put_unused_fd() and the dma_buf reference is dropped with dma_buf_put(), leaving no user- visible state behind. 4. dma_buf_fd_install() publishes the fd and emits the trace_dma_buf_fd tracepoint -- from here on the ioctl cannot fail. A new dma_buf_fd_install() helper is introduced in dma-buf.c to wrap fd_install() together with the DMA_BUF_TRACE() call, preserving the export tracing that dma_buf_fd() provides. dma_heap_ioctl_allocate() is refactored to return the struct dma_buf * directly (returning ERR_PTR on failure) so the caller holds the dmabuf reference across steps 3 and 4. The failure at step 3 is easily reachable from userspace: pass a struct dma_heap_allocation_data that lives in a page whose protection is flipped to PROT_READ between copy_from_user() and copy_to_user() (e.g. via mprotect()). Before this change each such ioctl leaks one dmabuf fd; after it, the fd table is unchanged on failure and only /dev/dma_heap/ remains open. No UAPI or heap-driver interface change. [1] https://lore.kernel.org/dri-devel/175e98de-f414-47d7-81c1-c0fe0a8f7f62@amd.com/ Fixes: c02a81fba74f ("dma-buf: Add dma-buf heaps framework") Cc: stable@vger.kernel.org Reviewed-by: T.J. Mercier Acked-by: Christian König Acked-by: Sumit Semwal Signed-off-by: Baineng Shou Link: https://lore.kernel.org/r/20260817050457.1005285-2-shoubaineng@gmail.com Signed-off-by: Christian König --- drivers/dma-buf/dma-buf.c | 20 ++++++++++ drivers/dma-buf/dma-heap.c | 80 +++++++++++++++++++------------------- include/linux/dma-buf.h | 1 + 3 files changed, 61 insertions(+), 40 deletions(-) diff --git a/drivers/dma-buf/dma-buf.c b/drivers/dma-buf/dma-buf.c index d504c636dc29..4c9add51f9ef 100644 --- a/drivers/dma-buf/dma-buf.c +++ b/drivers/dma-buf/dma-buf.c @@ -803,6 +803,26 @@ int dma_buf_fd(struct dma_buf *dmabuf, int flags) } EXPORT_SYMBOL_NS_GPL(dma_buf_fd, "DMA_BUF"); +/** + * dma_buf_fd_install - install a reserved fd for a dma-buf + * @dmabuf: [in] pointer to dma_buf + * @fd: [in] fd reserved with get_unused_fd_flags() + * + * Publishes a previously reserved fd into the caller's fd table. + * Must only be called after all fallible work (e.g. copy_to_user) + * has succeeded, as it cannot be undone safely once called. + * + * The caller is responsible for having emitted the trace event + * (via dma_buf_fd() or get_unused_fd_flags() + this function) + * before calling this. + */ +void dma_buf_fd_install(struct dma_buf *dmabuf, int fd) +{ + DMA_BUF_TRACE(trace_dma_buf_fd, dmabuf, fd); + fd_install(fd, dmabuf->file); +} +EXPORT_SYMBOL_NS_GPL(dma_buf_fd_install, "DMA_BUF"); + /** * dma_buf_get - returns the struct dma_buf related to an fd * @fd: [in] fd associated with the struct dma_buf to be returned diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c index a76bf3f8b071..43c32fb28313 100644 --- a/drivers/dma-buf/dma-heap.c +++ b/drivers/dma-buf/dma-heap.c @@ -55,33 +55,6 @@ MODULE_PARM_DESC(mem_accounting, "Enable cgroup-based memory accounting for dma-buf heap allocations (default=false)."); EXPORT_SYMBOL_NS_GPL(mem_accounting, "DMA_BUF_HEAP"); -static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len, - u32 fd_flags, - u64 heap_flags) -{ - struct dma_buf *dmabuf; - int fd; - - /* - * Allocations from all heaps have to begin - * and end on page boundaries. - */ - len = PAGE_ALIGN(len); - if (!len) - return -EINVAL; - - dmabuf = heap->ops->allocate(heap, len, fd_flags, heap_flags); - if (IS_ERR(dmabuf)) - return PTR_ERR(dmabuf); - - fd = dma_buf_fd(dmabuf, fd_flags); - if (fd < 0) { - dma_buf_put(dmabuf); - /* just return, as put will call release and that will free */ - } - return fd; -} - static int dma_heap_open(struct inode *inode, struct file *file) { struct dma_heap *heap; @@ -99,30 +72,42 @@ static int dma_heap_open(struct inode *inode, struct file *file) return 0; } -static long dma_heap_ioctl_allocate(struct file *file, void *data) +static struct dma_buf *dma_heap_ioctl_allocate(struct file *file, void *data) { struct dma_heap_allocation_data *heap_allocation = data; struct dma_heap *heap = file->private_data; + struct dma_buf *dmabuf; int fd; + size_t len; if (heap_allocation->fd) - return -EINVAL; + return ERR_PTR(-EINVAL); if (heap_allocation->fd_flags & ~DMA_HEAP_VALID_FD_FLAGS) - return -EINVAL; + return ERR_PTR(-EINVAL); if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS) - return -EINVAL; + return ERR_PTR(-EINVAL); - fd = dma_heap_buffer_alloc(heap, heap_allocation->len, - heap_allocation->fd_flags, - heap_allocation->heap_flags); - if (fd < 0) - return fd; + len = PAGE_ALIGN(heap_allocation->len); + if (!len) + return ERR_PTR(-EINVAL); + + dmabuf = heap->ops->allocate(heap, len, heap_allocation->fd_flags, + heap_allocation->heap_flags); + + if (IS_ERR(dmabuf)) + return dmabuf; + + fd = get_unused_fd_flags(heap_allocation->fd_flags); + if (fd < 0) { + dma_buf_put(dmabuf); + return ERR_PTR(fd); + } heap_allocation->fd = fd; - return 0; + return dmabuf; } static unsigned int dma_heap_ioctl_cmds[] = { @@ -138,6 +123,8 @@ static long dma_heap_ioctl(struct file *file, unsigned int ucmd, unsigned int in_size, out_size, drv_size, ksize; int nr = _IOC_NR(ucmd); int ret = 0; + int fd; + struct dma_buf *dmabuf; if (nr >= ARRAY_SIZE(dma_heap_ioctl_cmds)) return -EINVAL; @@ -174,15 +161,28 @@ static long dma_heap_ioctl(struct file *file, unsigned int ucmd, switch (kcmd) { case DMA_HEAP_IOCTL_ALLOC: - ret = dma_heap_ioctl_allocate(file, kdata); + dmabuf = dma_heap_ioctl_allocate(file, kdata); + + if (IS_ERR(dmabuf)) { + ret = PTR_ERR(dmabuf); + break; + } + + fd = ((struct dma_heap_allocation_data *)kdata)->fd; + if (copy_to_user((void __user *)arg, kdata, out_size) != 0) { + put_unused_fd(fd); + dma_buf_put(dmabuf); + ret = -EFAULT; + } else { + dma_buf_fd_install(dmabuf, fd); + } + break; default: ret = -ENOTTY; goto err; } - if (copy_to_user((void __user *)arg, kdata, out_size) != 0) - ret = -EFAULT; err: if (kdata != stack_kdata) kfree(kdata); diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h index d1203da56fc5..d15b2b31d3c9 100644 --- a/include/linux/dma-buf.h +++ b/include/linux/dma-buf.h @@ -567,6 +567,7 @@ void dma_buf_unpin(struct dma_buf_attachment *attach); struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info); int dma_buf_fd(struct dma_buf *dmabuf, int flags); +void dma_buf_fd_install(struct dma_buf *dmabuf, int fd); struct dma_buf *dma_buf_get(int fd); void dma_buf_put(struct dma_buf *dmabuf); From a4a1a2bfcb29785292d634d7787edc6fb550714d Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Mon, 17 Aug 2026 13:04:55 +0800 Subject: [PATCH 12/53] misc: fastrpc: don't publish fd before copy_to_user() succeeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fastrpc_ioctl_alloc_dmabuf() calls dma_buf_fd() which installs the fd into the caller's fd table before copy_to_user() copies the fd number back to userspace. If copy_to_user() fails, the fd is already visible to other threads in the same process but the ioctl returns -EFAULT. The existing comment in the code even acknowledges the problem: "The usercopy failed, but we can't do much about it, as dma_buf_fd() already called fd_install()..." Now that dma_buf_fd_install() is available (introduced to fix the same issue in dma-heap), apply the same pattern here: reserve the fd with get_unused_fd_flags(), attempt copy_to_user(), and only on success call dma_buf_fd_install() to publish it atomically with the tracepoint. On copy_to_user() failure, put_unused_fd() and dma_buf_put() cleanly unwind without any user-visible side effects. Fixes: 6cffd79504ce ("misc: fastrpc: Add support for dmabuf exporter") Cc: stable@vger.kernel.org Acked-by: Christian König Acked-by: Sumit Semwal Signed-off-by: Baineng Shou Link: https://lore.kernel.org/r/20260817050457.1005285-3-shoubaineng@gmail.com Signed-off-by: Christian König --- drivers/misc/fastrpc.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index eb6c2a78d3c7..d7a9e12f5575 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -1712,24 +1712,20 @@ static int fastrpc_dmabuf_alloc(struct fastrpc_user *fl, char __user *argp) return err; } - bp.fd = dma_buf_fd(buf->dmabuf, O_ACCMODE); + bp.fd = get_unused_fd_flags(O_ACCMODE); if (bp.fd < 0) { dma_buf_put(buf->dmabuf); - return -EINVAL; + return bp.fd; } if (copy_to_user(argp, &bp, sizeof(bp))) { - /* - * The usercopy failed, but we can't do much about it, as - * dma_buf_fd() already called fd_install() and made the - * file descriptor accessible for the current process. It - * might already be closed and dmabuf no longer valid when - * we reach this point. Therefore "leak" the fd and rely on - * the process exit path to do any required cleanup. - */ + put_unused_fd(bp.fd); + dma_buf_put(buf->dmabuf); return -EFAULT; } + dma_buf_fd_install(buf->dmabuf, bp.fd); + return 0; } From 3e164bf592bbbdde269c5cadc60a96f69cc6eed7 Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Mon, 17 Aug 2026 13:04:56 +0800 Subject: [PATCH 13/53] drm/prime: use dma_buf_fd_install() to preserve export tracing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_gem_prime_handle_to_fd() open-codes fd reservation and install using get_unused_fd_flags() + fd_install() directly. This bypasses the DMA_BUF_TRACE() call that dma_buf_fd() emits, so observability tools relying on the trace_dma_buf_fd tracepoint silently miss all DRM PRIME exports. Replace the bare fd_install() with dma_buf_fd_install(), which wraps fd_install() together with DMA_BUF_TRACE(), restoring full tracepoint coverage. No functional change; the fd lifecycle (get_unused_fd_flags → work → install) is already correct. Note: this patch depends on dma_buf_fd_install() introduced in "dma-buf: dma-heap: don't publish fd before copy_to_user() succeeds" [1]. [1] https://lore.kernel.org/dri-devel/20260714114654.3885457-2-shoubaineng@gmail.com/ Suggested-by: Christian König Acked-by: Sumit Semwal Reviewed-by: Christian König Signed-off-by: Baineng Shou Link: https://lore.kernel.org/r/20260817050457.1005285-4-shoubaineng@gmail.com Signed-off-by: Christian König --- drivers/gpu/drm/drm_prime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_prime.c b/drivers/gpu/drm/drm_prime.c index 9b44c78cd77f..fe3436d1235d 100644 --- a/drivers/gpu/drm/drm_prime.c +++ b/drivers/gpu/drm/drm_prime.c @@ -524,7 +524,7 @@ int drm_gem_prime_handle_to_fd(struct drm_device *dev, return PTR_ERR(dmabuf); } - fd_install(fd, dmabuf->file); + dma_buf_fd_install(dmabuf, fd); *prime_fd = fd; return 0; } From 8985cbc927fd3e23dfebaa099ae0b6d2b38d3258 Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Mon, 17 Aug 2026 13:04:57 +0800 Subject: [PATCH 14/53] selftests: dmabuf-heaps: add fd-leak-on-EFAULT regression test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a test case that verifies no file descriptor is leaked when DMA_HEAP_IOCTL_ALLOC succeeds internally but copy_to_user() fails to deliver the fd number back to userspace. The failure is triggered by placing the ioctl argument in a private anonymous page and flipping it to PROT_READ (via mprotect) between the kernel's copy_from_user() and copy_to_user() calls. With the buggy kernel the ioctl returns -EFAULT but leaves an extra open fd in the process's fd table; with the fixed kernel the fd count is unchanged. This serves as a regression test for: "dma-buf: dma-heap: don't publish fd before copy_to_user() succeeds" Suggested-by: Sumit Semwal Reviewed-by: T.J. Mercier Acked-by: Sumit Semwal Signed-off-by: Baineng Shou Link: https://lore.kernel.org/r/20260817050457.1005285-5-shoubaineng@gmail.com Signed-off-by: Christian König --- .../selftests/dmabuf-heaps/dmabuf-heap.c | 113 +++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/dmabuf-heaps/dmabuf-heap.c b/tools/testing/selftests/dmabuf-heaps/dmabuf-heap.c index fc9694fc4e89..1d49df671919 100644 --- a/tools/testing/selftests/dmabuf-heaps/dmabuf-heap.c +++ b/tools/testing/selftests/dmabuf-heaps/dmabuf-heap.c @@ -390,6 +390,116 @@ static void test_alloc_errors(char *heap_name) close(heap_fd); } +/* + * count_open_fds - return the number of open file descriptors. + * + * The fd opened by opendir() itself is counted, but since it is opened + * and closed within each call, it cancels out when comparing two counts. + * Returns -1 on error. + */ +static int count_open_fds(void) +{ + DIR *d = opendir("/proc/self/fd"); + struct dirent *de; + int count = 0; + + if (!d) + return -1; + + while ((de = readdir(d))) + if (de->d_name[0] != '.') + count++; + closedir(d); + return count; +} + +/* + * test_alloc_no_fd_leak_on_efault - verify no fd is leaked when + * copy_to_user() fails during DMA_HEAP_IOCTL_ALLOC. + * + * The bug: dma_buf_fd() called fd_install() before copy_to_user(). + * If copy_to_user() then failed (e.g. via mprotect), the fd was + * silently installed in the fd table but never returned to userspace. + * + * The fix: reserve the fd with get_unused_fd_flags() first, attempt + * copy_to_user(), and only call fd_install() on success. + * + * We trigger the failure by placing the ioctl argument in a private + * anonymous page and flipping it to PROT_READ before the ioctl. + * Inside the kernel, copy_from_user() reads from the page (reads are + * allowed under PROT_READ, so it succeeds), but copy_to_user() that + * writes the fd number back faults, returning -EFAULT. We then + * count open file descriptors before and after; with the bug an extra + * fd is left in the table. + */ +static void test_alloc_no_fd_leak_on_efault(char *heap_name) +{ + int heap_fd = -1; + int fd_before, fd_after; + int ret; + long page_size; + struct dma_heap_allocation_data *req; + + ksft_print_msg("Testing fd leak when copy_to_user() fails:\n"); + + heap_fd = dmabuf_heap_open(heap_name); + + page_size = sysconf(_SC_PAGESIZE); + + /* + * Place the ioctl argument in its own private anonymous page so + * we can flip its protection independently. + */ + req = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (req == MAP_FAILED) { + ksft_test_result_fail("mmap failed: %s\n", strerror(errno)); + goto out; + } + + memset(req, 0, sizeof(*req)); + req->len = page_size; + req->fd_flags = O_RDWR | O_CLOEXEC; + + fd_before = count_open_fds(); + if (fd_before < 0) { + ksft_test_result_fail("count_open_fds: %s\n", strerror(errno)); + munmap(req, page_size); + goto out; + } + + /* + * Make the page read-only so copy_to_user() will fault. The + * ioctl must fail with -1; if it returns success the test setup + * is broken (mprotect is synchronous, so there is no race). + */ + mprotect(req, page_size, PROT_READ); + + ret = ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, req); + + /* Re-allow writes so munmap can clean up */ + mprotect(req, page_size, PROT_READ | PROT_WRITE); + munmap(req, page_size); + + if (ret != -1) { + ksft_test_result_fail("ioctl returned %d, expected -1 EFAULT\n", + ret); + goto out; + } + + fd_after = count_open_fds(); + if (fd_after < 0) { + ksft_test_result_fail("count_open_fds: %s\n", strerror(errno)); + goto out; + } + + ksft_test_result(fd_before == fd_after, + "fd leak on EFAULT: before=%d after=%d\n", + fd_before, fd_after); +out: + close(heap_fd); +} + static int numer_of_heaps(void) { DIR *d = opendir(DEVPATH); @@ -420,7 +530,7 @@ int main(void) return KSFT_SKIP; } - ksft_set_plan(11 * numer_of_heaps()); + ksft_set_plan(12 * numer_of_heaps()); while ((dir = readdir(d))) { if (!strncmp(dir->d_name, ".", 2)) @@ -435,6 +545,7 @@ int main(void) test_alloc_zeroed(dir->d_name, ONE_MEG); test_alloc_compat(dir->d_name); test_alloc_errors(dir->d_name); + test_alloc_no_fd_leak_on_efault(dir->d_name); } closedir(d); From b3709d354545e70388177500761f92d906c4dfd6 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Thu, 20 Aug 2026 20:35:43 -0700 Subject: [PATCH 15/53] accel/amdxdna: Remove __counted_by from struct amdxdna_cmd_chain struct amdxdna_cmd_chain contains a flexible array annotated with __counted_by(command_count). Since the structure is stored in shared AMDXDNA_BO_SHARE memory, userspace can modify command_count concurrently. If command_count is changed to zero, the bounds check generated from __counted_by may fail and trigger a kernel panic. Remove __counted_by to avoid relying on the userspace-controlled command_count for the flexible array bounds check. Fixes: aac243092b70 ("accel/amdxdna: Add command execution") Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260821033543.1839719-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_ctx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/amdxdna_ctx.h b/drivers/accel/amdxdna/amdxdna_ctx.h index b6bef3af7dab..6e78bab8a02c 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.h +++ b/drivers/accel/amdxdna/amdxdna_ctx.h @@ -55,7 +55,7 @@ struct amdxdna_cmd_chain { u32 submit_index; u32 error_index; u32 reserved[3]; - u64 data[] __counted_by(command_count); + u64 data[]; }; /* From ef6d27af71e1dc43181ec797a6aaa77c27c36786 Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Tue, 18 Aug 2026 03:00:19 +0300 Subject: [PATCH 16/53] accel/amdxdna: reject a command chain that carries no commands A chain whose command_count is zero passes the payload length check, because struct_size(payload, data, 0) is just the header. The fill loop then does not run, so offset stays zero and the request is submitted with a zero-length buffer. On firmware without AIE2_NPU_COMMAND that ends at the opcode check, since op is still ERT_INVALID_CMD and aie2_get_chain_msg_op() answers MSG_OP_MAX_OPCODE. aie2_get_npu_chain_msg_op() answers MSG_OP_CHAIN_EXEC_NPU whatever it is given, so there the submission continues to drm_clflush_virt_range(cmd_buf, 0), which reads the byte before the buffer and faults on the vmap guard page. EXEC_CMD is reachable by any process that can open the render node. Reject the request instead. Fixes: 8ed8b0239617 ("accel/amdxdna: Add debug prints for command submission") Signed-off-by: Taimuraz Kaitmazov Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260818000019.369366-1-taimuraz@kaitmazov.com --- drivers/accel/amdxdna/aie2_message.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/aie2_message.c b/drivers/accel/amdxdna/aie2_message.c index dfe0fbdf066d..b4c49259a1a2 100644 --- a/drivers/accel/amdxdna/aie2_message.c +++ b/drivers/accel/amdxdna/aie2_message.c @@ -994,7 +994,7 @@ int aie2_cmdlist_multi_execbuf(struct amdxdna_hwctx *hwctx, } ccnt = payload->command_count; - if (payload_len < struct_size(payload, data, ccnt)) { + if (!ccnt || payload_len < struct_size(payload, data, ccnt)) { XDNA_DBG(xdna, "Invalid command count %d", ccnt); return -EINVAL; } From 7e33ba3a1d48c2d20ed270dec9d2d08332585c8e Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Thu, 20 Aug 2026 02:08:52 +0300 Subject: [PATCH 17/53] accel/amdxdna: put the chained BO when its mapping fails amdxdna_cmd_set_error() looks up the first BO of a command chain, which takes a reference, and drops it at the end of the function. The mapping of that BO is established in between, and the failure path returns without the put, so the reference is leaked. Ordinary use does not reach it. The chain has been submitted before any of this runs, so aie2_cmdlist_fill_slot() has already called amdxdna_cmd_get_op() on that BO and amdxdna_gem_vmap() has cached its address. What makes it reachable is that the BO is resolved again by handle here, and the handle is userspace's to recycle: closing it after submission and importing a dma-buf whose exporter implements no vmap onto the same id leaves amdxdna_gem_get_obj() returning an object this cannot map, since prime_import() types every import AMDXDNA_BO_SHARE. Fixes: d76856beb4a4 ("accel/amdxdna: Refactor GEM BO handling and add helper APIs for address retrieval") Signed-off-by: Taimuraz Kaitmazov Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260819230852.287751-1-taimuraz@kaitmazov.com --- drivers/accel/amdxdna/amdxdna_ctx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/amdxdna_ctx.c b/drivers/accel/amdxdna/amdxdna_ctx.c index 31a414c3f0d9..888e857ec558 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.c +++ b/drivers/accel/amdxdna/amdxdna_ctx.c @@ -183,8 +183,10 @@ int amdxdna_cmd_set_error(struct amdxdna_gem_obj *abo, if (!abo) return -EINVAL; cmd = amdxdna_gem_vmap(abo); - if (!cmd) + if (!cmd) { + amdxdna_gem_put_obj(abo); return -ENOMEM; + } } memset(cmd->data, 0xff, abo->mem.size - sizeof(*cmd)); From 7ab64476a610fe65858fdc37c7a30caa13e334ac Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Thu, 16 Jul 2026 14:52:19 +0800 Subject: [PATCH 18/53] accel/ethosu: check MMIO mapping errors in probe devm_platform_ioremap_resource() returns an error pointer when the register resource cannot be mapped. ethosu_probe() stores it and continues until initialization dereferences it through MMIO accessors. Return the mapping error before initializing the device. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Signed-off-by: GuoHan Zhao Link: https://patch.msgid.link/20260716065219.931088-1-zhaoguohan@kylinos.cn Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c index ed9c748a54ad..b2901eb8a7a0 100644 --- a/drivers/accel/ethosu/ethosu_drv.c +++ b/drivers/accel/ethosu/ethosu_drv.c @@ -342,6 +342,8 @@ static int ethosu_probe(struct platform_device *pdev) dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); ethosudev->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ethosudev->regs)) + return PTR_ERR(ethosudev->regs); ethosudev->num_clks = devm_clk_bulk_get_all(&pdev->dev, ðosudev->clks); if (ethosudev->num_clks < 0) From db9deec5a345abc538d081fb221dc0b00a9695bd Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Mon, 24 Aug 2026 17:26:11 +0200 Subject: [PATCH 19/53] accel: ethosu: Don't read the U65 rounding mode as a storage mode Bits 15:14 of NPU_SET_{IFM,OFM}_PRECISION select the activation storage mode on U85 only. On U65 the same field holds the rounding mode, and the command stream parser has read it as a storage mode since the driver was added. That went unnoticed while unknown values fell through the switch, but now that they are rejected, every U65 command stream that asks for natural rounding (2) fails CMDSTREAM_BO_CREATE with -EINVAL. Mesa emits it for average pooling, concatenation, split, unpack, strided slice, LUT and argmax, which is 72 failures of the Teflon test suite on an i.MX93. Truncating rounding (1) is misread as well: it picks the two-tile address path and computes a bogus feature map size from tile bases the command stream never set. Read the field as a storage mode only on the hardware where it is one. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Fixes: 6b7e0066294d ("accel: ethosu: Handle U85 internal chaining buffer") Assisted-by: Claude:claude-opus-5 Signed-off-by: Tomeu Vizoso Link: https://patch.msgid.link/20260824152612.751007-1-tomeu@tomeuvizoso.net Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_gem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c index d50fed64d4d9..fa37a190e9ff 100644 --- a/drivers/accel/ethosu/ethosu_gem.c +++ b/drivers/accel/ethosu/ethosu_gem.c @@ -204,7 +204,7 @@ static u64 feat_matrix_length(struct ethosu_device *edev, struct feat_matrix *fm, u32 x, u32 y, u32 c, bool ofm) { - u32 element_size, storage = fm->precision >> 14; + u32 element_size, storage = ethosu_is_u65(edev) ? 0 : fm->precision >> 14; int tile = 0; u64 addr; From 554ab79cfd104fbd76f3be82b0dfa8fc2b324799 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 28 Apr 2026 23:03:40 -0400 Subject: [PATCH 20/53] drm/nouveau/disp/r535: Add scanline position support + head state support That's right! It looks like this never actually got finished, something which I just noticed today when I saw this fun message spamming one of my test machine's kernel logs when enabling display debug output for nouveau: [drm:drm_crtc_vblank_helper_get_vblank_timestamp_internal] crtc 0 : scanoutpos query failed. So it looks like we've been falling back to DRM's core fallback for a while now, whoops. So, while it seems that we do have the option of doing this through GSP - that doesn't seem like a great idea. Mainly because reading this from GSP would involve a lot more latency then we should have for vblank handling due to the RPC communication. So instead of implementing that, just use gv100_head_state and gv100_head_rgpos for implementing .state and .rgpos. It seems to work perfectly fine! Fixes: 9e9944449023 ("drm/nouveau/disp/r535: initial support") Cc: Ben Skeggs Cc: Dave Airlie Cc: Timur Tabi Cc: Ben Skeggs Cc: James Jones Cc: Faith Ekstrand Cc: Suraj Kandpal Cc: Lyude Paul Cc: Aaron Kling Cc: Danilo Krummrich Cc: Zhang Enpei Cc: # v6.7+ Signed-off-by: Lyude Paul Signed-off-by: Dave Airlie Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260429030348.3930866-1-lyude@redhat.com (cherry picked from commit 804cb093b245c752f15d17186e0d404f10303593) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c | 4 ++-- drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h | 2 ++ drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 8 ++------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c index dbd984da7501..0608266188d3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c @@ -253,7 +253,7 @@ gv100_head_vblank_get(struct nvkm_head *head) nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000004, 0x00000004); } -static void +void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline) { struct nvkm_device *device = head->disp->engine.subdev.device; @@ -263,7 +263,7 @@ gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline) *hline = nvkm_rd32(device, 0x616334 + hoff) & 0x0000ffff; } -static void +void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state) { struct nvkm_device *device = head->disp->engine.subdev.device; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h index 856252bf559a..b642729c254f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h @@ -53,6 +53,8 @@ void gf119_head_rgclk(struct nvkm_head *, int); int gv100_head_cnt(struct nvkm_disp *, unsigned long *); int gv100_head_new(struct nvkm_disp *, int id); +void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state); +void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline); #define HEAD_MSG(h,l,f,a...) do { \ struct nvkm_head *_h = (h); \ diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index 1155f079b0c3..e77733a5d9c3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -625,14 +625,10 @@ r535_head_vblank_get(struct nvkm_head *head) nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002); } -static void -r535_head_state(struct nvkm_head *head, struct nvkm_head_state *state) -{ -} - static const struct nvkm_head_func r535_head = { - .state = r535_head_state, + .state = gv100_head_state, + .rgpos = gv100_head_rgpos, .vblank_get = r535_head_vblank_get, .vblank_put = r535_head_vblank_put, }; From c6659e0ffc19b4ef0b3273c185cb8409a154eada Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:13:59 +0400 Subject: [PATCH 21/53] drm/nouveau/disp: move GSP head-timing ISR and vblank helpers to tu102.c The GSP-RM display code in rm/r535/disp.c owns a handful of direct MMIO routines: the head-timing (vblank) interrupt handler and the per-head vblank enable/disable. They program display registers, not RM, so they belong with the rest of the per-chip register code in engine/disp/. Move them to tu102.c (Turing is the first GSP-capable generation) as tu102_disp_intr() and tu102_head_vblank_get()/put(), exported for rm/r535/disp.c, which keeps calling them by name for now. No functional change. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-2-mohamedahmedegypt2001@gmail.com --- .../gpu/drm/nouveau/nvkm/engine/disp/head.h | 3 ++ .../gpu/drm/nouveau/nvkm/engine/disp/priv.h | 1 + .../gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 50 ++++++++++++++++++ .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 52 ++----------------- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h index b642729c254f..986043e87554 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h @@ -56,6 +56,9 @@ int gv100_head_new(struct nvkm_disp *, int id); void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state); void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline); +void tu102_head_vblank_get(struct nvkm_head *); +void tu102_head_vblank_put(struct nvkm_head *); + #define HEAD_MSG(h,l,f,a...) do { \ struct nvkm_head *_h = (h); \ nvkm_##l(&_h->disp->engine.subdev, "head-%d: "f"\n", _h->id, ##a); \ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h index a3fd7cb7c488..722ec340e12a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h @@ -72,6 +72,7 @@ int gv100_disp_wndw_cnt(struct nvkm_disp *, unsigned long *); int gv100_disp_caps_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **); int tu102_disp_init(struct nvkm_disp *); +irqreturn_t tu102_disp_intr(struct nvkm_inth *); void nv50_disp_dptmds_war_2(struct nvkm_disp *, struct dcb_output *); void nv50_disp_dptmds_war_3(struct nvkm_disp *, struct dcb_output *); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c index dcb9f8ba374c..7b70b466fa36 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c @@ -104,6 +104,56 @@ tu102_sor_new(struct nvkm_disp *disp, int id) return nvkm_ior_new_(&tu102_sor, disp, SOR, id, hda & BIT(id)); } +/* The GSP-RM display path leaves head-timing (vblank) interrupts and their + * enables to us. These program the RM head-timing line (bit 1 of the + * per-head enable, not the bit nvkm's own gv100 path uses). + */ +void +tu102_head_vblank_put(struct nvkm_head *head) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + + nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000); +} + +void +tu102_head_vblank_get(struct nvkm_head *head) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + + nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002); + nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002); +} + +static void +tu102_disp_intr_head_timing(struct nvkm_disp *disp, int head) +{ + struct nvkm_subdev *subdev = &disp->engine.subdev; + struct nvkm_device *device = subdev->device; + u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04)); + + if (stat & 0x00000002) { + nvkm_disp_vblank(disp, head); + + nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002); + } +} + +irqreturn_t +tu102_disp_intr(struct nvkm_inth *inth) +{ + struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth); + struct nvkm_subdev *subdev = &disp->engine.subdev; + struct nvkm_device *device = subdev->device; + unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff; + int head; + + for_each_set_bit(head, &mask, 8) + tu102_disp_intr_head_timing(disp, head); + + return IRQ_HANDLED; +} + int tu102_disp_init(struct nvkm_disp *disp) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index e77733a5d9c3..8e57bb6519e5 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -608,29 +608,12 @@ r535_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask) return 4; } -static void -r535_head_vblank_put(struct nvkm_head *head) -{ - struct nvkm_device *device = head->disp->engine.subdev.device; - - nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000); -} - -static void -r535_head_vblank_get(struct nvkm_head *head) -{ - struct nvkm_device *device = head->disp->engine.subdev.device; - - nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002); - nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002); -} - static const struct nvkm_head_func r535_head = { .state = gv100_head_state, .rgpos = gv100_head_rgpos, - .vblank_get = r535_head_vblank_get, - .vblank_put = r535_head_vblank_put, + .vblank_get = tu102_head_vblank_get, + .vblank_put = tu102_head_vblank_put, }; static struct nvkm_conn * @@ -1404,35 +1387,6 @@ static const struct nvkm_event_func r535_disp_event = { }; -static void -r535_disp_intr_head_timing(struct nvkm_disp *disp, int head) -{ - struct nvkm_subdev *subdev = &disp->engine.subdev; - struct nvkm_device *device = subdev->device; - u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04)); - - if (stat & 0x00000002) { - nvkm_disp_vblank(disp, head); - - nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002); - } -} - -static irqreturn_t -r535_disp_intr(struct nvkm_inth *inth) -{ - struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth); - struct nvkm_subdev *subdev = &disp->engine.subdev; - struct nvkm_device *device = subdev->device; - unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff; - int head; - - for_each_set_bit(head, &mask, 8) - r535_disp_intr_head_timing(disp, head); - - return IRQ_HANDLED; -} - static void r535_disp_fini(struct nvkm_disp *disp, bool suspend) { @@ -1708,7 +1662,7 @@ r535_disp_oneinit(struct nvkm_disp *disp) return ret; ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev, - r535_disp_intr, &disp->engine.subdev.inth); + tu102_disp_intr, &disp->engine.subdev.inth); if (ret) return ret; From eb1ffc3dc72d379a41e367a44b99fb61a15bf8ba Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:00 +0400 Subject: [PATCH 22/53] drm/nouveau/disp: move the GSP HDMI GCP AVMute write to engine/disp r535_sor_hdmi_audio() pairs two RM controls (a SET_OD_PACKET carrying the same General Control Packet, and the audio mute-stream toggle) with a direct write of the GCP AVMute bit through the SF GCP unit. The controls are RM and stay, but the direct write is register programming and moves next to the other per-chip display code as tu102_sor_hdmi_gcp(). No functional change. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-3-mohamedahmedegypt2001@gmail.com --- drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h | 1 + drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 15 +++++++++++++++ .../drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 9 +-------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h index 3ba04bead2f9..5d682a774f2d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h @@ -194,6 +194,7 @@ void gv100_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32); void gv100_sor_dp_watermark(struct nvkm_ior *, int, u8); extern const struct nvkm_ior_func_hda gv100_sor_hda; +void tu102_sor_hdmi_gcp(struct nvkm_ior *, int, bool); void tu102_sor_dp_vcpi(struct nvkm_ior *, int, u8, u8, u16, u16); int nv50_pior_cnt(struct nvkm_disp *, unsigned long *); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c index 7b70b466fa36..6cfd52c9056f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c @@ -30,6 +30,21 @@ #include +/* General Control Packet: bracket an audio enable/disable with AVMute + * through the legacy GCP SF unit. Used by the GSP-RM path, which sends the + * equivalent packet via RM as well but keeps the direct write in sync. + */ +void +tu102_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable) +{ + struct nvkm_device *device = sor->disp->engine.subdev.device; + const u32 hdmi = head * 0x400; + + nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000000); + nvkm_wr32(device, 0x6f00cc + hdmi, !enable ? 0x00000001 : 0x00000010); + nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001); +} + void tu102_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index 8e57bb6519e5..cd4451e62512 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -545,16 +545,9 @@ r535_sor_hdmi_ctrl_audio(struct nvkm_outp *outp, bool enable) static void r535_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable) { - struct nvkm_device *device = sor->disp->engine.subdev.device; - const u32 hdmi = head * 0x400; - r535_sor_hdmi_ctrl_audio(sor->asy.outp, enable); r535_sor_hdmi_ctrl_audio_mute(sor->asy.outp, !enable); - - /* General Control (GCP). */ - nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000000); - nvkm_wr32(device, 0x6f00cc + hdmi, !enable ? 0x00000001 : 0x00000010); - nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001); + tu102_sor_hdmi_gcp(sor, head, enable); } static void From 9886aad51f4b5e7082209a153e404bcd8101356c Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:01 +0400 Subject: [PATCH 23/53] drm/nouveau/disp: route GSP-RM display MMIO through nvkm_disp_func hooks The GSP-RM display code in rm/r535/disp.c borrows a few register-programming routines from engine/disp (the head-timing interrupt handler, vblank enables, armed head state and scanout position readback, the AVI/VSI infoframe writers and the GCP AVMute write) and so far picked them by name, which means it has to know which chip it runs on the moment a generation changes any of them. Give nvkm_disp_func a .gsp table that each chip fills with exactly those hooks, add tu102_gsp_disp (TU1xx) and ga102_gsp_disp (GA10x onwards) carrying the current functions, hand them to r535_disp_new() instead of the full hardware tables, and make rm/r535/disp.c call through the hooks. The head hooks are a whole nvkm_head_func, so r535_head goes away and the chip's own table is handed to nvkm_head_new_(). r535_sor_hdmi gets infoframe forwarders, r535_sor_hdmi_audio() calls the GCP hook, and the interrupt handler comes from the table. The tables are per chip even though the two currently coincide, so a generation that changes a hook only touches its own file. rm/r535/disp.c no longer contains chip-specific register code, and a new display generation only has to provide its own table. No functional change. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-4-mohamedahmedegypt2001@gmail.com --- .../gpu/drm/nouveau/nvkm/engine/disp/ga102.c | 13 +++++++- .../gpu/drm/nouveau/nvkm/engine/disp/head.h | 1 + .../gpu/drm/nouveau/nvkm/engine/disp/priv.h | 14 +++++++++ .../gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 21 ++++++++++++- .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 31 +++++++++++-------- 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c index ab0a85c92430..820834b5ee9b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c @@ -144,12 +144,23 @@ ga102_disp = { }, }; +static const struct nvkm_disp_func +ga102_gsp_disp = { + .uevent = &gv100_disp_chan_uevent, + .ramht_size = 0x2000, + .gsp.intr = tu102_disp_intr, + .gsp.head = &tu102_gsp_head, + .gsp.hdmi_gcp = tu102_sor_hdmi_gcp, + .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi, + .gsp.hdmi_infoframe_vsi = gv100_sor_hdmi_infoframe_vsi, +}; + int ga102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, struct nvkm_disp **pdisp) { if (nvkm_gsp_rm(device->gsp)) - return r535_disp_new(&ga102_disp, device, type, inst, pdisp); + return r535_disp_new(&ga102_gsp_disp, device, type, inst, pdisp); return nvkm_disp_new_(&ga102_disp, device, type, inst, pdisp); } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h index 986043e87554..784521c2aca1 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h @@ -58,6 +58,7 @@ void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline); void tu102_head_vblank_get(struct nvkm_head *); void tu102_head_vblank_put(struct nvkm_head *); +extern const struct nvkm_head_func tu102_gsp_head; #define HEAD_MSG(h,l,f,a...) do { \ struct nvkm_head *_h = (h); \ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h index 722ec340e12a..a9dbda67a7d4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h @@ -5,6 +5,8 @@ #include #include struct nvkm_head; +struct nvkm_head_func; +struct nvkm_ior; struct nvkm_outp; struct dcb_output; @@ -34,6 +36,18 @@ struct nvkm_disp_func { int (*new)(struct nvkm_disp *, int id); } wndw, head, dac, sor, pior; + /* Register programming that the GSP-RM display path (rm/r535) needs from + * the chip, everything else on that path goes through RM. The hooks are + * called unconditionally and the head table is handed to nvkm_head_new_(). + */ + struct { + irqreturn_t (*intr)(struct nvkm_inth *); + const struct nvkm_head_func *head; + void (*hdmi_gcp)(struct nvkm_ior *, int head, bool enable); + void (*hdmi_infoframe_avi)(struct nvkm_ior *, int head, void *data, u32 size); + void (*hdmi_infoframe_vsi)(struct nvkm_ior *, int head, void *data, u32 size); + } gsp; + u16 ramht_size; struct nvkm_sclass root; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c index 6cfd52c9056f..948b1d2f954c 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c @@ -140,6 +140,14 @@ tu102_head_vblank_get(struct nvkm_head *head) nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002); } +const struct nvkm_head_func +tu102_gsp_head = { + .state = gv100_head_state, + .rgpos = gv100_head_rgpos, + .vblank_get = tu102_head_vblank_get, + .vblank_put = tu102_head_vblank_put, +}; + static void tu102_disp_intr_head_timing(struct nvkm_disp *disp, int head) { @@ -295,12 +303,23 @@ tu102_disp = { }, }; +static const struct nvkm_disp_func +tu102_gsp_disp = { + .uevent = &gv100_disp_chan_uevent, + .ramht_size = 0x2000, + .gsp.intr = tu102_disp_intr, + .gsp.head = &tu102_gsp_head, + .gsp.hdmi_gcp = tu102_sor_hdmi_gcp, + .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi, + .gsp.hdmi_infoframe_vsi = gv100_sor_hdmi_infoframe_vsi, +}; + int tu102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, struct nvkm_disp **pdisp) { if (nvkm_gsp_rm(device->gsp)) - return r535_disp_new(&tu102_disp, device, type, inst, pdisp); + return r535_disp_new(&tu102_gsp_disp, device, type, inst, pdisp); return nvkm_disp_new_(&tu102_disp, device, type, inst, pdisp); } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index cd4451e62512..bf97edcdfc95 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -547,7 +547,19 @@ r535_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable) { r535_sor_hdmi_ctrl_audio(sor->asy.outp, enable); r535_sor_hdmi_ctrl_audio_mute(sor->asy.outp, !enable); - tu102_sor_hdmi_gcp(sor, head, enable); + sor->disp->func->gsp.hdmi_gcp(sor, head, enable); +} + +static void +r535_sor_hdmi_infoframe_avi(struct nvkm_ior *sor, int head, void *data, u32 size) +{ + sor->disp->func->gsp.hdmi_infoframe_avi(sor, head, data, size); +} + +static void +r535_sor_hdmi_infoframe_vsi(struct nvkm_ior *sor, int head, void *data, u32 size) +{ + sor->disp->func->gsp.hdmi_infoframe_vsi(sor, head, data, size); } static void @@ -575,8 +587,8 @@ r535_sor_hdmi = { .ctrl = r535_sor_hdmi_ctrl, .scdc = r535_sor_hdmi_scdc, /*TODO: SF_USER -> KMS. */ - .infoframe_avi = gv100_sor_hdmi_infoframe_avi, - .infoframe_vsi = gv100_sor_hdmi_infoframe_vsi, + .infoframe_avi = r535_sor_hdmi_infoframe_avi, + .infoframe_vsi = r535_sor_hdmi_infoframe_vsi, .audio = r535_sor_hdmi_audio, }; @@ -601,14 +613,6 @@ r535_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask) return 4; } -static const struct nvkm_head_func -r535_head = { - .state = gv100_head_state, - .rgpos = gv100_head_rgpos, - .vblank_get = tu102_head_vblank_get, - .vblank_put = tu102_head_vblank_put, -}; - static struct nvkm_conn * r535_conn_new(struct nvkm_disp *disp, u32 id) { @@ -1606,7 +1610,7 @@ r535_disp_oneinit(struct nvkm_disp *disp) nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); for_each_set_bit(i, &disp->head.mask, disp->head.nr) { - ret = nvkm_head_new_(&r535_head, disp, i); + ret = nvkm_head_new_(disp->func->gsp.head, disp, i); if (ret) return ret; } @@ -1655,7 +1659,7 @@ r535_disp_oneinit(struct nvkm_disp *disp) return ret; ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev, - tu102_disp_intr, &disp->engine.subdev.inth); + disp->func->gsp.intr, &disp->engine.subdev.inth); if (ret) return ret; @@ -1688,6 +1692,7 @@ r535_disp_new(const struct nvkm_disp_func *hw, struct nvkm_device *device, rm->uevent = hw->uevent; rm->sor.cnt = r535_sor_cnt; rm->sor.new = r535_sor_new; + rm->gsp = hw->gsp; rm->ramht_size = hw->ramht_size; rm->root.oclass = gpu->disp.class.root; From 92f09dcb4e8473ab25764e950994ab7b6abce6dd Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:02 +0400 Subject: [PATCH 24/53] drm/nouveau/disp: fix HDMI vendor infoframes on GB20x The GSP path reuses the GV100 direct-MMIO infoframe writers on every chip. On GB20x that is only half right as while the legacy SF AVI unit is unchanged, the legacy VSI unit at 0x6f0100 was removed, so gv100_sor_hdmi_infoframe_vsi() writes into a reserved area and no vendor infoframe ever reaches the HW. This affects HDMI-VIC signalling which can impact some 4K modes for legacy HDMI 1.4 sinks. GB20x (NVDisplay 5.0+) reorganised the SF HDMI packet units. Per NVIDIA's published C971/CA71 DISP_SF_USER class headers, only three legacy units remain (AVI at +0x000, GCP at +0x040, ACR at +0x080), and vendor infoframes must instead be sent through the shared generic infoframe units at +0x130, whose 9-dword packet slots are loaded through the shared data port at +0x3f0/+0x3f4. Add a VSI writer using the same programming sequence OpenRM uses on these chips (nvhdmipkt_C971.c, programAdvancedInfoframeC971()): disable the unit and wait for it to idle, clear the SENT status, write the packet through the data port with a zero inserted in HB3 after the three header bytes, then enable the unit for every-frame transmission during vblank. Generic unit 1 is used for the VSI, matching the slot assignment in NVIDIA's nvkms (NVHDMIPKT_TYPE_SHARED_GENERIC2, unit 0 is reserved for extended metadata packets and unit 2 for the HDR DRM infoframe, if those are wired up later). GB20x so far shared GA10x's display entry point. Give it its own, gb202_disp_new(), with a gb202_gsp_disp table that supplies the VSI writer to the GSP path and otherwise carries the same hooks as GA10x. The following fixes fill in the rest of the GB20x differences there. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-5-mohamedahmedegypt2001@gmail.com --- .../drm/nouveau/include/nvkm/engine/disp.h | 1 + .../gpu/drm/nouveau/nvkm/engine/device/base.c | 10 +-- .../gpu/drm/nouveau/nvkm/engine/disp/Kbuild | 1 + .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 88 +++++++++++++++++++ 4 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c diff --git a/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h b/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h index 7903d7470d19..01145db32c53 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h @@ -87,4 +87,5 @@ int gp102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct int gv100_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **); int tu102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **); int ga102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **); +int gb202_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **); #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c index ea62dc97f118..96c8a5b29999 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c @@ -2846,7 +2846,7 @@ nv1b2_chipset = { .pci = { 0x00000001, gh100_pci_new }, .timer = { 0x00000001, gk20a_timer_new }, .vfn = { 0x00000001, ga100_vfn_new }, - .disp = { 0x00000001, ga102_disp_new }, + .disp = { 0x00000001, gb202_disp_new }, .fifo = { 0x00000001, ga102_fifo_new }, }; @@ -2862,7 +2862,7 @@ nv1b3_chipset = { .pci = { 0x00000001, gh100_pci_new }, .timer = { 0x00000001, gk20a_timer_new }, .vfn = { 0x00000001, ga100_vfn_new }, - .disp = { 0x00000001, ga102_disp_new }, + .disp = { 0x00000001, gb202_disp_new }, .fifo = { 0x00000001, ga102_fifo_new }, }; @@ -2878,7 +2878,7 @@ nv1b5_chipset = { .pci = { 0x00000001, gh100_pci_new }, .timer = { 0x00000001, gk20a_timer_new }, .vfn = { 0x00000001, ga100_vfn_new }, - .disp = { 0x00000001, ga102_disp_new }, + .disp = { 0x00000001, gb202_disp_new }, .fifo = { 0x00000001, ga102_fifo_new }, }; @@ -2894,7 +2894,7 @@ nv1b6_chipset = { .pci = { 0x00000001, gh100_pci_new }, .timer = { 0x00000001, gk20a_timer_new }, .vfn = { 0x00000001, ga100_vfn_new }, - .disp = { 0x00000001, ga102_disp_new }, + .disp = { 0x00000001, gb202_disp_new }, .fifo = { 0x00000001, ga102_fifo_new }, }; @@ -2910,7 +2910,7 @@ nv1b7_chipset = { .pci = { 0x00000001, gh100_pci_new }, .timer = { 0x00000001, gk20a_timer_new }, .vfn = { 0x00000001, ga100_vfn_new }, - .disp = { 0x00000001, ga102_disp_new }, + .disp = { 0x00000001, gb202_disp_new }, .fifo = { 0x00000001, ga102_fifo_new }, }; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild index e1aecd3fe96c..98d6ca5ac311 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild @@ -27,6 +27,7 @@ nvkm-y += nvkm/engine/disp/gp102.o nvkm-y += nvkm/engine/disp/gv100.o nvkm-y += nvkm/engine/disp/tu102.o nvkm-y += nvkm/engine/disp/ga102.o +nvkm-y += nvkm/engine/disp/gb202.o nvkm-y += nvkm/engine/disp/udisp.o nvkm-y += nvkm/engine/disp/uconn.o diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c new file mode 100644 index 000000000000..1e40de83e2bb --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 Valve Corp. + */ +#include "priv.h" +#include "head.h" +#include "ior.h" + +#include + +/* GB20x (NVD5.0) reorganised the SF HDMI packet units. The AVI unit is + * unchanged from GV100, but the legacy VSI unit is gone. Vendor infoframes + * are sent through the shared generic infoframe units instead. Register + * layout per NVIDIA's clc971.h/clca71.h, programming sequence per + * nvhdmipkt_C971.c:programAdvancedInfoframeC971(). + */ +static void +gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size) +{ + struct nvkm_device *device = ior->disp->engine.subdev.device; + const u32 hoff = head * 0x400; + /* Generic infoframe unit 1, the slot NVIDIA's driver uses for the VSI. */ + const u32 ctrl = 0x6f0138 + hoff; + u8 buf[36] = {}; + int i; + + /* Disable the unit and wait for it to go idle. */ + nvkm_mask(device, ctrl, 0x00000001, 0x00000000); + if (nvkm_msec(device, 2000, + if (!(nvkm_rd32(device, ctrl) & 0x00400000)) + break; + ) < 0) + return; + + if (!size) + return; + + /* Clear SENT status, and point the data port at unit 1's slot. */ + nvkm_mask(device, ctrl, 0x00800000, 0x00800000); + nvkm_wr32(device, 0x6f03f0 + hoff, 0x00000001); + + /* The data port takes the raw packet, except that a zero is inserted + * in HB3 after the three header bytes. A slot is 9 dwords (HB0-3 plus + * up to 32 payload bytes). An HDMI infoframe carries at most PB0-27, + * so the tail stays zero, and we always write the whole slot. + */ + size = min_t(u32, size, 31); + memcpy(buf, data, min_t(u32, size, 3)); + if (size > 3) + memcpy(&buf[4], (u8 *)data + 3, size - 3); + + for (i = 0; i < 36; i += 4) { + nvkm_wr32(device, 0x6f03f4 + hoff, buf[i + 0] | buf[i + 1] << 8 | + buf[i + 2] << 16 | + (u32)buf[i + 3] << 24); + } + + /* No flip ID or scanline matching. */ + nvkm_wr32(device, 0x6f013c + hoff, 0x00000000); + + /* ENABLE | RUN_MODE=ALWAYS | LOC=VBLANK | OFFSET=1 | SIZE=0. */ + nvkm_wr32(device, ctrl, 0x00000041); + + /* Audio priority low (the init value). */ + nvkm_wr32(device, 0x6f03f8 + hoff, 0x00000002); +} + +/* GB20x is GSP-only. This table supplies the register programming the + * GSP-RM display path needs from the chip. + */ +static const struct nvkm_disp_func +gb202_gsp_disp = { + .uevent = &gv100_disp_chan_uevent, + .ramht_size = 0x2000, + .gsp.intr = tu102_disp_intr, + .gsp.head = &tu102_gsp_head, + .gsp.hdmi_gcp = tu102_sor_hdmi_gcp, + /* The legacy AVI unit is unchanged on GB20x. */ + .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi, + .gsp.hdmi_infoframe_vsi = gb202_sor_hdmi_infoframe_vsi, +}; + +int +gb202_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, + struct nvkm_disp **pdisp) +{ + return r535_disp_new(&gb202_gsp_disp, device, type, inst, pdisp); +} From 764deff8450c9a83e335c17c32ea258ec25bb71e Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:03 +0400 Subject: [PATCH 25/53] drm/nouveau/disp: fix HDMI GCP AVMute register offsets on GB20x The GSP path brackets audio enablement with a General Control Packet AVMute toggle. r535_sor_hdmi_audio() calls the gsp.hdmi_gcp hook, which every chip so far serves with tu102_sor_hdmi_gcp() and the legacy GCP unit at 0x6f00c0/0x6f00cc. On GB20x the SF packet units were compacted and the old generic and VSI units are gone (ACR keeps slot 2) and the GCP unit moved from slot 3 to slot 1 (control 0x6f0040 and subpack 0x6f004c from NVIDIA's published clc971.h. The same offsets are also used by OpenRM's hdmiWriteGeneralCtrlPacketC871() on these chips). The old addresses are reserved on GB20x, so the AVMute writes were silent no-ops and mitigated only by the equivalent GCP r535_sor_hdmi_audio() already sends through the SET_OD_PACKET RM control. Add a GB20x GCP writer using the new offsets and hook it into gb202_gsp_disp, keeping the direct MMIO path in sync with the hardware as on earlier chips. Only SB0 (the AVMute bit) is written. On NVD5.0 the subpack register also carries SB1_CTRL (bit 24), which selects where the deep-color CD/PP fields are generated (hardware or from the driver, with the default being HW). hdmiWriteGeneralCtrlPacketC871() likewise writes only SB0-SB2. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-6-mohamedahmedegypt2001@gmail.com --- .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c index 1e40de83e2bb..face801af080 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c @@ -65,6 +65,24 @@ gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 siz nvkm_wr32(device, 0x6f03f8 + hoff, 0x00000002); } +/* General Control Packet AVMute bracket. The GCP unit moved to slot 1 on + * NVD5.0. Only SB0 (the AVMute bit) is ours to write so we must not do a + * full write here: SB1 carries the deep-color CD/PP fields, and SB1_CTRL + * (bit 24, new with clc871.h) controls where their generation happens (HW + * or driver) on these chips, with the default being HW. + */ +static void +gb202_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable) +{ + struct nvkm_device *device = sor->disp->engine.subdev.device; + const u32 hdmi = head * 0x400; + + nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000000); + nvkm_mask(device, 0x6f004c + hdmi, 0x000000ff, !enable ? 0x00000001 : + 0x00000010); + nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000001); +} + /* GB20x is GSP-only. This table supplies the register programming the * GSP-RM display path needs from the chip. */ @@ -74,7 +92,7 @@ gb202_gsp_disp = { .ramht_size = 0x2000, .gsp.intr = tu102_disp_intr, .gsp.head = &tu102_gsp_head, - .gsp.hdmi_gcp = tu102_sor_hdmi_gcp, + .gsp.hdmi_gcp = gb202_sor_hdmi_gcp, /* The legacy AVI unit is unchanged on GB20x. */ .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi, .gsp.hdmi_infoframe_vsi = gb202_sor_hdmi_infoframe_vsi, From 39fd4b742720c68da8695ee1ffa85c5fea4f8e11 Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:04 +0400 Subject: [PATCH 26/53] drm/nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware NVIDIA removed the deprecated actualPclkHz/linkClkFreqHz fields and the whole Legacy{activeCnt, activeFrac, activePolarity, mvidWarEnabled, MvidWarParams} block from the SST sub-struct of NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS between the 535 and 570 releases (compared in OpenRM tags 535.113.01 vs 570.144), shrinking the struct. Everything nouveau writes sits at identical offsets in both layouts except the trailing SST.bEnableAudioOverRightPanel (written as zero), but the size is wrong on r570, which means r535_sor_dp_sst() and r535_sor_dp_vcpi() are sent with an incorrect size. Route the .sst/.vcpi IOR functions through nvkm_rm_api_disp the same way bl_ctrl and dp.get_caps/set_indexed_link_rates already are. Keep the existing implementation for r535 and add an r570 implementation built against the 570.144 layout, which already exists in r570/nvrm/disp.h but was unused until now. Also add the NV0073_CTRL_CMD_DP_CONFIG_STREAM define that was missing from the layout. Other DP controls sent through shared r535 code did not change layout between the tags. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-7-mohamedahmedegypt2001@gmail.com --- .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 33 ++++++++-- .../nouveau/nvkm/subdev/gsp/rm/r570/disp.c | 64 +++++++++++++++++++ .../nvkm/subdev/gsp/rm/r570/nvrm/disp.h | 2 + .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 5 ++ 4 files changed, 97 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index bf97edcdfc95..7d1d4ee2af79 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -400,16 +400,16 @@ r535_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable) r535_sor_dp_audio_mute(sor, false); } -static void -r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn) +static int +r535_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn) { struct nvkm_disp *disp = sor->disp; struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl; ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl)); - if (WARN_ON(IS_ERR(ctrl))) - return; + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); ctrl->subDeviceInstance = 0; ctrl->head = head; @@ -429,12 +429,20 @@ r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u ctrl->MST.sendACT = 0; ctrl->MST.singleHeadMSTPipeline = 0; ctrl->MST.bEnableAudioOverRightPanel = 0; - WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + +static void +r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn) +{ + const struct nvkm_rm_api *rmapi = sor->disp->engine.subdev.device->gsp->rm->api; + + WARN_ON(rmapi->disp->dp.vcpi(sor, head, slot, slot_nr, pbn, aligned_pbn)); } static int -r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef, - u32 watermark, u32 hblanksym, u32 vblanksym) +r535_dp_sst(struct nvkm_ior *sor, int head, bool ef, + u32 watermark, u32 hblanksym, u32 vblanksym) { struct nvkm_disp *disp = sor->disp; struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl; @@ -461,6 +469,15 @@ r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef, return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); } +static int +r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef, + u32 watermark, u32 hblanksym, u32 vblanksym) +{ + const struct nvkm_rm_api *rmapi = sor->disp->engine.subdev.device->gsp->rm->api; + + return rmapi->disp->dp.sst(sor, head, ef, watermark, hblanksym, vblanksym); +} + static const struct nvkm_ior_func_dp r535_sor_dp = { .sst = r535_sor_dp_sst, @@ -1734,6 +1751,8 @@ r535_disp = { .dp = { .get_caps = r535_dp_get_caps, .set_indexed_link_rates = r535_dp_set_indexed_link_rates, + .sst = r535_dp_sst, + .vcpi = r535_dp_vcpi, }, .chan = { .set_pushbuf = r535_disp_chan_set_pushbuf, diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c index a96e31c2d80b..8a23837f356e 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c @@ -5,6 +5,7 @@ #include #include +#include #include #include "nvhw/drf.h" @@ -74,6 +75,67 @@ r570_disp_chan_set_pushbuf(struct nvkm_disp *disp, s32 oclass, int inst, struct return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); } +static int +r570_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->head = head; + ctrl->sorIndex = sor->id; + ctrl->dpLink = sor->asy.link == 2; + ctrl->bEnableOverride = 1; + ctrl->bMST = 1; + ctrl->hBlankSym = 0; + ctrl->vBlankSym = 0; + ctrl->colorFormat = 0; + ctrl->bEnableTwoHeadOneOr = 0; + ctrl->singleHeadMultistreamMode = 0; + ctrl->MST.slotStart = slot; + ctrl->MST.slotEnd = slot + slot_nr - 1; + ctrl->MST.PBN = pbn; + ctrl->MST.Timeslice = aligned_pbn; + ctrl->MST.sendACT = 0; + ctrl->MST.singleHeadMSTPipeline = 0; + ctrl->MST.bEnableAudioOverRightPanel = 0; + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + +static int +r570_dp_sst(struct nvkm_ior *sor, int head, bool ef, + u32 watermark, u32 hblanksym, u32 vblanksym) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->head = head; + ctrl->sorIndex = sor->id; + ctrl->dpLink = sor->asy.link == 2; + ctrl->bEnableOverride = 1; + ctrl->bMST = 0; + ctrl->hBlankSym = hblanksym; + ctrl->vBlankSym = vblanksym; + ctrl->colorFormat = 0; + ctrl->bEnableTwoHeadOneOr = 0; + ctrl->SST.bEnhancedFraming = ef; + ctrl->SST.tuSize = 64; + ctrl->SST.waterMark = watermark; + ctrl->SST.bEnableAudioOverRightPanel = 0; + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + static int r570_dp_set_indexed_link_rates(struct nvkm_outp *outp) { @@ -255,6 +317,8 @@ r570_disp = { .dp = { .get_caps = r570_dp_get_caps, .set_indexed_link_rates = r570_dp_set_indexed_link_rates, + .sst = r570_dp_sst, + .vcpi = r570_dp_vcpi, }, .chan = { .set_pushbuf = r570_disp_chan_set_pushbuf, diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h index 06e972835d77..742b25a2a12d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h @@ -256,6 +256,8 @@ typedef struct NV0073_CTRL_DP_CTRL_PARAMS { NvU32 eightLaneDpcdBaseAddr; } NV0073_CTRL_DP_CTRL_PARAMS; +#define NV0073_CTRL_CMD_DP_CONFIG_STREAM (0x731362U) /* finn: Evaluated from "(FINN_NV04_DISPLAY_COMMON_DP_INTERFACE_ID << 8) | NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS_MESSAGE_ID" */ + typedef struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS { NvU32 subDeviceInstance; NvU32 head; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index a9af94adf9ef..fcd0221dcea1 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -6,6 +6,7 @@ #ifndef __NVKM_RM_H__ #define __NVKM_RM_H__ #include "handles.h" +struct nvkm_ior; struct nvkm_outp; struct r535_gr; @@ -93,6 +94,10 @@ struct nvkm_rm_api { struct { int (*get_caps)(struct nvkm_disp *, int *link_bw, bool *mst, bool *wm); int (*set_indexed_link_rates)(struct nvkm_outp *); + int (*sst)(struct nvkm_ior *, int head, bool ef, + u32 watermark, u32 hblanksym, u32 vblanksym); + int (*vcpi)(struct nvkm_ior *, int head, + u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn); } dp; struct { From 9421dfe912e55360e6b9301a110acb00df7e7320 Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:05 +0400 Subject: [PATCH 27/53] drm/nouveau/disp: fix head state readback on GB20x The GSP path reads armed head state and the RG scanout position through gv100_head_state() and gv100_head_rgpos() on every generation. gv100_head_state() reads the core channel's state mirror at a 0x400 per-head stride, which NVD5.0 (GB20x) doubled. Per NVIDIA's published CA7D class header every HEAD_SET method sits at 0x2000 + head * 0x800, while the mirror bases are unchanged (assembly at 0x680000, armed at +0x8000, per OpenRM's v03_00 channel-user-base HAL which is still used on DISPv0502). Add gb202_head_state(), the same readback at the 0x800 stride, and a gb202_gsp_head table to supply it. gv100_head_rgpos() is kept. The RG registers keep their per-head 0x800 stride on NVD5.0, and OpenRM's kdispReadRgLineCountAndFrameCount_v03_00 still reads NV_PDISP_RG_DPCA on DISPv0502. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-8-mohamedahmedegypt2001@gmail.com --- .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c index face801af080..765c42039a47 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c @@ -83,6 +83,55 @@ gb202_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable) nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000001); } +/* Same core-channel state mirror as gv100_head_state() (assembly at 0x680000, + * armed at +0x8000, per-head method offsets unchanged), but NVD5.0 spaces + * heads 0x800 apart (see NVCA7D_HEAD_SET_*(a) in clca7d.h). + */ +static void +gb202_head_state(struct nvkm_head *head, struct nvkm_head_state *state) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + const u32 hoff = (state == &head->arm) * 0x8000 + head->id * 0x800; + u32 data; + + data = nvkm_rd32(device, 0x682064 + hoff); + state->vtotal = (data & 0xffff0000) >> 16; + state->htotal = (data & 0x0000ffff); + data = nvkm_rd32(device, 0x682068 + hoff); + state->vsynce = (data & 0xffff0000) >> 16; + state->hsynce = (data & 0x0000ffff); + data = nvkm_rd32(device, 0x68206c + hoff); + state->vblanke = (data & 0xffff0000) >> 16; + state->hblanke = (data & 0x0000ffff); + data = nvkm_rd32(device, 0x682070 + hoff); + state->vblanks = (data & 0xffff0000) >> 16; + state->hblanks = (data & 0x0000ffff); + /* Bit 31 is ADJ1000DIV1001, not a HERTZ bit. We don't have enough bits + * to add the full clock in hz on Blackwell (35 bits), but state->hz + * is unused and obsolete under GSP so this is fine. + */ + state->hz = nvkm_rd32(device, 0x68200c + hoff) & 0x7fffffff; + + data = nvkm_rd32(device, 0x682004 + hoff); + switch ((data & 0x000000f0) >> 4) { + case 5: state->or.depth = 30; break; + case 4: state->or.depth = 24; break; + case 1: state->or.depth = 18; break; + default: + state->or.depth = 18; + WARN_ON(1); + break; + } +} + +static const struct nvkm_head_func +gb202_gsp_head = { + .state = gb202_head_state, + .rgpos = gv100_head_rgpos, + .vblank_get = tu102_head_vblank_get, + .vblank_put = tu102_head_vblank_put, +}; + /* GB20x is GSP-only. This table supplies the register programming the * GSP-RM display path needs from the chip. */ @@ -91,7 +140,7 @@ gb202_gsp_disp = { .uevent = &gv100_disp_chan_uevent, .ramht_size = 0x2000, .gsp.intr = tu102_disp_intr, - .gsp.head = &tu102_gsp_head, + .gsp.head = &gb202_gsp_head, .gsp.hdmi_gcp = gb202_sor_hdmi_gcp, /* The legacy AVI unit is unchanged on GB20x. */ .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi, From 5bb489b333237c1bf63a891a4362986253a0060a Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:06 +0400 Subject: [PATCH 28/53] drm/nouveau/gsp: fix vblank interrupts on GB20x The GSP path programs per-head timing (vblank) interrupts the same way on every generation. NVD5.0 (GB20x) reworked the FE interrupt frontend around four message-based kernel vectors (high latency, low latency, PMU, and GSP) and moved RM head-timing interrupts to the dedicated low-latency vector: - The enable is NV_PDISP_FE_RM_INTR_EN1_HEAD_TIMING, 0x611ef0 + head*4 (570.144 kernel_head_0501.c, renamed kernel_head_0502.c from 575.51.02 on, and v05_01 dev_disp.h). - The vector is reported as a separate interrupt table entry, MC_ENGINE_IDX_DISP_LOW (intr_gb202.c, intrCacheDispIntrVectors). - The vector must be re-armed through NV_PDISP_FE_INTR_RETRIGGER(1) at 0x611f34 after servicing (kdispServiceInterrupt -> kdispIntrRetrigger_v05_01). The event latch (0x611800), per-head status (0x611c00), and dispatch summary (0x611ec0) the interrupt handler uses are unchanged on GB20x (kheadReadPendingVblank_v03_00 and kheadResetPendingLastData_v03_00 remain for DISPv0502+). On GB20x the old code enables head timing onto the legacy vector, leaves its handler there, and never re-arms the message-based vectors. Page flips still complete (nv50 sends those events from the commit path), so the desktop looks fine while DRM vblank waits and vblank sequence queries are affected. Supply GB20x vblank enables and an interrupt handler that re-arms the vector after servicing through gb202_gsp_disp, translate the low-latency interrupt table entry as a second NVKM_ENGINE_DISP instance, and add a gsp.intr_low_latency flag so r535_disp_oneinit() attaches the handler to that instance. GB20x was the last cross-file user of the TU1xx vblank enables, so make those static and drop their head.h prototypes. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-9-mohamedahmedegypt2001@gmail.com --- .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 42 +++++++++++++++++-- .../gpu/drm/nouveau/nvkm/engine/disp/head.h | 2 - .../gpu/drm/nouveau/nvkm/engine/disp/priv.h | 2 + .../gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 4 +- .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 10 ++++- .../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 9 ++++ 6 files changed, 61 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c index 765c42039a47..d0360610f9fa 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c @@ -124,12 +124,46 @@ gb202_head_state(struct nvkm_head *head, struct nvkm_head_state *state) } } +/* NVD5.0 (GB20x and later) moved the RM head-timing interrupt enable to + * the low-latency vector's EN1 block. The event latch is unchanged. + */ +static void +gb202_head_vblank_put(struct nvkm_head *head) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + + nvkm_mask(device, 0x611ef0 + (head->id * 4), 0x00000002, 0x00000000); +} + +static void +gb202_head_vblank_get(struct nvkm_head *head) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + + nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002); + nvkm_mask(device, 0x611ef0 + (head->id * 4), 0x00000002, 0x00000002); +} + +static irqreturn_t +gb202_disp_intr(struct nvkm_inth *inth) +{ + struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth); + irqreturn_t ret = tu102_disp_intr(inth); + + /* The FE interrupt vectors are message-based on NVD5.0. Re-arm the + * low-latency vector so it fires again for any event that latched + * while we were servicing. + */ + nvkm_wr32(disp->engine.subdev.device, 0x611f34, 0x00000001); + return ret; +} + static const struct nvkm_head_func gb202_gsp_head = { .state = gb202_head_state, .rgpos = gv100_head_rgpos, - .vblank_get = tu102_head_vblank_get, - .vblank_put = tu102_head_vblank_put, + .vblank_get = gb202_head_vblank_get, + .vblank_put = gb202_head_vblank_put, }; /* GB20x is GSP-only. This table supplies the register programming the @@ -139,7 +173,9 @@ static const struct nvkm_disp_func gb202_gsp_disp = { .uevent = &gv100_disp_chan_uevent, .ramht_size = 0x2000, - .gsp.intr = tu102_disp_intr, + /* Head timing arrives on the dedicated low-latency vector. */ + .gsp.intr = gb202_disp_intr, + .gsp.intr_low_latency = true, .gsp.head = &gb202_gsp_head, .gsp.hdmi_gcp = gb202_sor_hdmi_gcp, /* The legacy AVI unit is unchanged on GB20x. */ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h index 784521c2aca1..5976498da909 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h @@ -56,8 +56,6 @@ int gv100_head_new(struct nvkm_disp *, int id); void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state); void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline); -void tu102_head_vblank_get(struct nvkm_head *); -void tu102_head_vblank_put(struct nvkm_head *); extern const struct nvkm_head_func tu102_gsp_head; #define HEAD_MSG(h,l,f,a...) do { \ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h index a9dbda67a7d4..fde321dbd7c8 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h @@ -42,6 +42,8 @@ struct nvkm_disp_func { */ struct { irqreturn_t (*intr)(struct nvkm_inth *); + /* Head-timing interrupts arrive on a second DISP vector. */ + bool intr_low_latency; const struct nvkm_head_func *head; void (*hdmi_gcp)(struct nvkm_ior *, int head, bool enable); void (*hdmi_infoframe_avi)(struct nvkm_ior *, int head, void *data, u32 size); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c index 948b1d2f954c..f6c163072ff6 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c @@ -123,7 +123,7 @@ tu102_sor_new(struct nvkm_disp *disp, int id) * enables to us. These program the RM head-timing line (bit 1 of the * per-head enable, not the bit nvkm's own gv100 path uses). */ -void +static void tu102_head_vblank_put(struct nvkm_head *head) { struct nvkm_device *device = head->disp->engine.subdev.device; @@ -131,7 +131,7 @@ tu102_head_vblank_put(struct nvkm_head *head) nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000); } -void +static void tu102_head_vblank_get(struct nvkm_head *head) { struct nvkm_device *device = head->disp->engine.subdev.device; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index 7d1d4ee2af79..f5f22173fc2c 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -1671,7 +1671,15 @@ r535_disp_oneinit(struct nvkm_disp *disp) if (ret) return ret; - ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, disp->engine.subdev.inst); + /* Chips that raise head-timing interrupts on a separate low-latency + * vector report it as a second DISP interrupt table entry, exposed + * as instance 1 by the RM engine-index translation (see + * r570_gsp_xlat_mc_engine_idx()). Their high-latency vector + * (instance 0) is left unhandled as no event nouveau enables is + * routed to it, and without a handler it stays masked. + */ + ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, + disp->func->gsp.intr_low_latency ? 1 : disp->engine.subdev.inst); if (ret < 0) return ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index 996941c668ba..1488771c63fc 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -44,6 +44,15 @@ r570_gsp_xlat_mc_engine_idx(u32 mc_engine_idx, enum nvkm_subdev_type *ptype, int *ptype = NVKM_ENGINE_DISP; *pinst = 0; return true; + case MC_ENGINE_IDX_DISP_LOW: + /* GB20x+ report a separate low-latency display vector, used + * for head-timing interrupts. Expose it as a second DISP + * interrupt instance. r535_disp_oneinit() attaches the + * handler to it when the chip's gsp.intr_low_latency is set. + */ + *ptype = NVKM_ENGINE_DISP; + *pinst = 1; + return true; case MC_ENGINE_IDX_CE0 ... MC_ENGINE_IDX_CE19: *ptype = NVKM_ENGINE_CE; *pinst = mc_engine_idx - MC_ENGINE_IDX_CE0; From c6f48e59ece0123f6a11527ad4d89b21c2d65b87 Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Tue, 25 Aug 2026 18:41:34 +0800 Subject: [PATCH 29/53] drm/sysfb: ofdrm: Fix integer overflow in fb_size calculation The framebuffer size calculation `fb_size = linebytes * height` can overflow when both values are large (e.g., 46341 * 46341 > INT_MAX). Since linebytes and height are both int types, the multiplication is performed as int * int, which results in undefined behavior on overflow. Use check_mul_overflow() to detect and prevent this overflow, consistent with the approach used in simpledrm.c and corebootdrm.c. Signed-off-by: Shixiong Ou Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Fixes: c8a17756c425 ("drm/ofdrm: Add ofdrm for Open Firmware framebuffers") Cc: # v6.2+ Link: https://patch.msgid.link/20260825104134.669676-1-oushixiong1025@163.com --- drivers/gpu/drm/sysfb/ofdrm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sysfb/ofdrm.c b/drivers/gpu/drm/sysfb/ofdrm.c index 819aed466727..a6dc34b9ec0f 100644 --- a/drivers/gpu/drm/sysfb/ofdrm.c +++ b/drivers/gpu/drm/sysfb/ofdrm.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -913,7 +914,10 @@ static struct ofdrm_device *ofdrm_device_create(struct drm_driver *drv, return ERR_PTR(-EINVAL); } - fb_size = linebytes * height; + if (check_mul_overflow(linebytes, height, &fb_size)) { + drm_err(dev, "framebuffer size exceeds maximum\n"); + return ERR_PTR(-EINVAL); + } /* * Try to figure out the address of the framebuffer. Unfortunately, Open From 958f35cbb8955ca3fa439cd9f2092cb42414aa8c Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Fri, 31 Jul 2026 19:17:29 +0800 Subject: [PATCH 30/53] drm/sysfb: ofdrm: Fix is_avivo() constant comparison bug The is_avivo() function has a logic error where it compares a constant to another constant instead of checking the device parameter: (PCI_VENDOR_ID_ATI_R600 >= 0x9400) Signed-off-by: Shixiong Ou Reviewed-by: Thomas Zimmermann Fixes: f496834e1674 ("drm/ofdrm: Add per-model device function") Signed-off-by: Thomas Zimmermann Cc: # v6.2+ Link: https://patch.msgid.link/20260731111729.703116-1-oushixiong1025@163.com --- drivers/gpu/drm/sysfb/ofdrm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sysfb/ofdrm.c b/drivers/gpu/drm/sysfb/ofdrm.c index a6dc34b9ec0f..9d60db45139c 100644 --- a/drivers/gpu/drm/sysfb/ofdrm.c +++ b/drivers/gpu/drm/sysfb/ofdrm.c @@ -239,7 +239,7 @@ static bool is_avivo(u32 vendor, u32 device) /* This will match most R5xx */ return (vendor == PCI_VENDOR_ID_ATI) && ((device >= PCI_VENDOR_ID_ATI_R520 && device < 0x7800) || - (PCI_VENDOR_ID_ATI_R600 >= 0x9400)); + (device >= PCI_VENDOR_ID_ATI_R600)); } static enum ofdrm_model display_get_model_of(struct drm_device *dev, struct device_node *of_node) From 92312d333bf700798f92f30406c721bce87506f3 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Tue, 25 Aug 2026 14:07:29 +0200 Subject: [PATCH 31/53] drm/cirrus-qemu: Validate BAR0 size during probe The `cirrus-qemu` driver relies on `CIRRUS_VRAM_SIZE` (4 MB) to validate framebuffer sizes. However, during PCI probe, the driver mapped BAR0 without verifying that its size matches `CIRRUS_VRAM_SIZE`. If a PCI device with a BAR0 smaller than 4 MB is bound to the driver, the mapped VRAM will be smaller than expected. Because validation checks assume 4 MB VRAM, framebuffers larger than the mapped memory can be created. When the display plane is updated (e.g. during release), `cirrus_primary_plane_helper_atomic_update()` copies the framebuffer to VRAM using `drm_fb_memcpy()`. Writing past the end of the mapped I/O memory causes a supervisor write page fault: BUG: unable to handle page fault for address: ffffc9000389c000 ... RIP: 0010:memcpy_toio+0x7c/0xe0 arch/x86/lib/iomem.c:110 ... Call Trace: iosys_map_memcpy_to include/linux/iosys-map.h:285 [inline] drm_fb_memcpy+0x325/0x5d0 drivers/gpu/drm/drm_format_helper.c:442 cirrus_primary_plane_helper_atomic_update+0x98a/0xb00 drivers/gpu/drm/tiny/cirrus-qemu.c:358 drm_atomic_helper_commit_planes+0x626/0xea0 drivers/gpu/drm/drm_atomic_helper.c:3038 drm_atomic_helper_commit_tail+0x60/0x510 drivers/gpu/drm/drm_atomic_helper.c:1989 commit_tail+0x2b1/0x3c0 drivers/gpu/drm/drm_atomic_helper.c:2074 drm_atomic_helper_commit+0xa77/0xb10 drivers/gpu/drm/drm_atomic_helper.c:2312 Fix this by validating in `cirrus_pci_probe()` that the PCI BAR0 resource is not less than `CIRRUS_VRAM_SIZE`, returning `-ENODEV` if it is less. Fixes: ab3e023b1b4c ("drm/cirrus: rewrite and modernize driver.") Assisted-by: Gemini:gemini-3.6-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+2442951a6abb004df963@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2442951a6abb004df963 Link: https://syzkaller.appspot.com/ai_job?id=ba262a3a-bccf-4ad8-a1b0-583c55d34fd6 Signed-off-by: Slawomir Stepien Signed-off-by: Thomas Zimmermann Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260825120729.493611-1-sst@poczta.fm --- drivers/gpu/drm/tiny/cirrus-qemu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/tiny/cirrus-qemu.c b/drivers/gpu/drm/tiny/cirrus-qemu.c index 075221b431d3..3bf23fcf6574 100644 --- a/drivers/gpu/drm/tiny/cirrus-qemu.c +++ b/drivers/gpu/drm/tiny/cirrus-qemu.c @@ -582,6 +582,9 @@ static int cirrus_pci_probe(struct pci_dev *pdev, struct cirrus_device *cirrus; int ret; + if (pci_resource_len(pdev, 0) < CIRRUS_VRAM_SIZE) + return -ENODEV; + ret = aperture_remove_conflicting_pci_devices(pdev, cirrus_driver.name); if (ret) return ret; From d32b08284f44c20edb2ea3f64ba0a6a165036fb2 Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Mon, 17 Aug 2026 15:45:20 -0300 Subject: [PATCH 32/53] drm/atomic: remove bogus check for file_priv Since file_priv can never be NULL at prepare_signaling() as it is only called by drm_mode_atomic_ioctl(), remove the check. If that was not the case, skipping the rest of the block here would cause the drm_pending_vblank_event object to leak and fail to set up the fence in case out_fence_ptr is set. Since the check is unreachable, there is no possible leak. Signed-off-by: Thadeu Lima de Souza Cascardo Reviewed-by: Melissa Wen Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260817-drm_atomic_bogus_check-v2-1-2b9e60f32a7e@igalia.com --- drivers/gpu/drm/drm_atomic_uapi.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index e997917819e8..657c15474ed5 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -1445,9 +1445,6 @@ static int prepare_signaling(struct drm_device *dev, if (arg->flags & DRM_MODE_PAGE_FLIP_EVENT) { struct drm_pending_vblank_event *e = crtc_state->event; - if (!file_priv) - continue; - ret = drm_event_reserve_init(dev, file_priv, &e->base, &e->event.base); if (ret) { From 4d4be202165e832d74849b4a68e289a2a377039c Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Mon, 27 Jul 2026 17:45:49 -0300 Subject: [PATCH 33/53] drm: Fix drm_crtc_commit leak if signaled when PAGE_FLIP_EVENT is used Commit 1c6ceeee6ebb ("drm/atomic: Fix memleak on ERESTARTSYS during non-blocking commits") fixed a very similar issue when the event was allocated by drm_atomic_helper_setup_commit() itself. However, if the event is allocated in prepare_signaling(), it will also be set to NULL in complete_signaling(), which prevents drm_crtc_commit from being put in __drm_atomic_helper_crtc_destroy_state(). Dropping the reference when the event is set to NULL at complete_signaling() fixes the leak. The leak can be reproduced by sending a signal to the thread using DRM_MODE_PAGE_FLIP_EVENT and using a sw_sync fence to cause the atomic ioctl to block at drm_atomic_helper_wait_for_fences(). It happened both with amdgpu and vkms. Fixes: 24835e442f28 ("drm: reference count event->completion") Cc: stable@vger.kernel.org Signed-off-by: Thadeu Lima de Souza Cascardo Reviewed-by: Melissa Wen Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260727-drm_crtc_atomic_commit_leak-v1-1-23d9948a9d7c@igalia.com --- drivers/gpu/drm/drm_atomic_uapi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index 657c15474ed5..ae7667d1072d 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -1560,6 +1560,8 @@ static void complete_signaling(struct drm_device *dev, * to prevent a double free in drm_atomic_commit_clear. */ if (event && (event->base.fence || event->base.file_priv)) { + if (crtc_state->commit && crtc_state->commit->abort_completion) + drm_crtc_commit_put(crtc_state->commit); drm_event_cancel_free(dev, &event->base); crtc_state->event = NULL; } From 2d2a3adc91950f9a18829dadc7317fb5180a15c5 Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Fri, 17 Jul 2026 14:11:45 +0800 Subject: [PATCH 34/53] accel/ethosu: fix job completion fence cleanup ethosu_ioctl_submit_job() allocates done_fence before validating buffer handles. Errors after allocation call ethosu_job_err_cleanup(), which frees the job but leaks the uninitialized fence. A scheduler dependency error also lets ethosu_job_run() return before dma_fence_init(). Normal cleanup then passes a zeroed refcount to dma_fence_put(). Release done_fence in the common cleanup path and use dma_fence_was_initialized() to distinguish initialized fences from raw allocations. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260716065219.931088-1-zhaoguohan@kylinos.cn?part=1 Signed-off-by: GuoHan Zhao Link: https://patch.msgid.link/20260717061145.1478139-6-zhaoguohan@kylinos.cn [robh: also fix goto] Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_job.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index 1e2465279aae..1e4b65f62933 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -152,6 +152,13 @@ static void ethosu_job_err_cleanup(struct ethosu_job *job) drm_gem_object_put(job->cmd_bo); + if (job->done_fence) { + if (dma_fence_was_initialized(job->done_fence)) + dma_fence_put(job->done_fence); + else + dma_fence_free(job->done_fence); + } + kfree(job); } @@ -162,7 +169,6 @@ static void ethosu_job_cleanup(struct kref *ref) pm_runtime_put_autosuspend(job->dev->base.dev); - dma_fence_put(job->done_fence); dma_fence_put(job->inference_done_fence); ethosu_job_err_cleanup(job); @@ -393,7 +399,7 @@ static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file ejob->done_fence = kzalloc_obj(*ejob->done_fence); if (!ejob->done_fence) { ret = -ENOMEM; - goto out_cleanup_job; + goto out_put_job; } ret = drm_sched_job_init(&ejob->base, From 20839d02c0cf7437bc508d4c5430538d9dc4f428 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 26 Aug 2026 12:54:21 +0200 Subject: [PATCH 35/53] drm/tegra: Add blend mode properties The default programming in the driver matches the "coverage" blend mode, so add the corresponding pixel blend mode property to let userspace know about it. Tested-by: Jon Hunter Acked-by: Jon Hunter Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260826105421.1825331-1-thierry.reding@kernel.org --- drivers/gpu/drm/tegra/dc.c | 6 ++++++ drivers/gpu/drm/tegra/hub.c | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 0b3fcc7011b3..fefc3761a4bc 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -904,6 +904,7 @@ static struct drm_plane *tegra_primary_plane_create(struct drm_device *drm, struct tegra_dc *dc) { unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm); + unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE); enum drm_plane_type type = DRM_PLANE_TYPE_PRIMARY; struct tegra_plane *plane; unsigned int num_formats; @@ -939,6 +940,7 @@ static struct drm_plane *tegra_primary_plane_create(struct drm_device *drm, } drm_plane_helper_add(&plane->base, &tegra_plane_helper_funcs); + drm_plane_create_blend_mode_property(&plane->base, blend_caps); drm_plane_create_zpos_property(&plane->base, plane->index, 0, 255); err = drm_plane_create_rotation_property(&plane->base, @@ -1209,6 +1211,7 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, struct tegra_dc *dc) { unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm); + unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE); struct tegra_plane *plane; unsigned int num_formats; const u32 *formats; @@ -1252,6 +1255,7 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, } drm_plane_helper_add(&plane->base, &tegra_cursor_plane_helper_funcs); + drm_plane_create_blend_mode_property(&plane->base, blend_caps); drm_plane_create_zpos_immutable_property(&plane->base, 255); return &plane->base; @@ -1356,6 +1360,7 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, bool cursor) { unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm); + unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE); struct tegra_plane *plane; unsigned int num_formats; enum drm_plane_type type; @@ -1394,6 +1399,7 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, } drm_plane_helper_add(&plane->base, &tegra_plane_helper_funcs); + drm_plane_create_blend_mode_property(&plane->base, blend_caps); drm_plane_create_zpos_property(&plane->base, plane->index, 0, 255); err = drm_plane_create_rotation_property(&plane->base, diff --git a/drivers/gpu/drm/tegra/hub.c b/drivers/gpu/drm/tegra/hub.c index bd442bfd4540..448f49f3a7d7 100644 --- a/drivers/gpu/drm/tegra/hub.c +++ b/drivers/gpu/drm/tegra/hub.c @@ -759,6 +759,7 @@ struct drm_plane *tegra_shared_plane_create(struct drm_device *drm, unsigned int index, enum drm_plane_type type) { + unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE); struct tegra_drm *tegra = drm->dev_private; struct tegra_display_hub *hub = tegra->hub; struct tegra_shared_plane *plane; @@ -797,6 +798,7 @@ struct drm_plane *tegra_shared_plane_create(struct drm_device *drm, } drm_plane_helper_add(p, &tegra_shared_plane_helper_funcs); + drm_plane_create_blend_mode_property(p, blend_caps); drm_plane_create_zpos_property(p, 0, 0, 255); return p; From c8329cb590df4a8b3a4e878d289d4b17824db8d1 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 30 Aug 2026 20:19:53 -0700 Subject: [PATCH 36/53] dma-buf: fix some kernel-doc warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - drop Excess description of @lock from kernel-doc - add missing function/macro short descriptions WARNING: include/linux/dma-fence-array.h:47 Excess struct member 'lock' description in 'dma_fence_array' WARNING: include/linux/dma-fence-chain.h:48 Excess struct member 'lock' description in 'dma_fence_chain' Warning: include/linux/dma-fence-chain.h:82 missing initial short description on line: * dma_fence_chain_alloc Warning: include/linux/dma-fence-chain.h:94 missing initial short description on line: * dma_fence_chain_free Fixes: 5943243914b9 ("dma-buf: use inline lock for the dma-fence-array") Fixes: a408c0ca0c41 ("dma-buf: use inline lock for the dma-fence-chain") Signed-off-by: Randy Dunlap Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260831031956.3410813-1-rdunlap@infradead.org --- include/linux/dma-fence-array.h | 1 - include/linux/dma-fence-chain.h | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/linux/dma-fence-array.h b/include/linux/dma-fence-array.h index 1b1d87579c38..0c49d7ccefb6 100644 --- a/include/linux/dma-fence-array.h +++ b/include/linux/dma-fence-array.h @@ -28,7 +28,6 @@ struct dma_fence_array_cb { /** * struct dma_fence_array - fence to represent an array of fences * @base: fence base class - * @lock: spinlock for fence handling * @num_fences: number of fences in the array * @num_pending: fences in the array still pending * @fences: array of the fences diff --git a/include/linux/dma-fence-chain.h b/include/linux/dma-fence-chain.h index df3beadf1515..705c4394ac0d 100644 --- a/include/linux/dma-fence-chain.h +++ b/include/linux/dma-fence-chain.h @@ -20,7 +20,6 @@ * @prev: previous fence of the chain * @prev_seqno: original previous seqno before garbage collection * @fence: encapsulated fence - * @lock: spinlock for fence handling */ struct dma_fence_chain { struct dma_fence base; @@ -81,9 +80,8 @@ dma_fence_chain_contained(struct dma_fence *fence) } /** - * dma_fence_chain_alloc - * - * Returns a new struct dma_fence_chain object or NULL on failure. + * dma_fence_chain_alloc - Returns a new &struct dma_fence_chain object or + * %NULL on failure. * * This specialized allocator has to be a macro for its allocations to be * accounted separately (to have a separate alloc_tag). The typecast is @@ -93,7 +91,8 @@ dma_fence_chain_contained(struct dma_fence *fence) kmalloc_obj(struct dma_fence_chain) /** - * dma_fence_chain_free + * dma_fence_chain_free - Frees an allocated but not used + * &struct dma_fence_chain object. * @chain: chain node to free * * Frees up an allocated but not used struct dma_fence_chain object. This From c4126f1db36e6b2e1c79b0e30a8a2de91c568f4c Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Mon, 10 Aug 2026 14:58:45 +0530 Subject: [PATCH 37/53] drm/pagemap: Prevent double migration of device pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A device-private folio migrated to system memory by a CPU fault can remain reachable through the raw-PFN eviction path until migration finalization drops the source reference. If eviction selects the same device-private folio during this window, it can attempt to migrate the folio again. The second migration can leave an uncharged folio on an LRU list, causing folio_lruvec_lock_irqsave() to retry indefinitely and resulting in a soft lockup and RCU stall. Mark successfully migrated device-private folios using a low bit of their zone_device_data before migration finalization. Make both CPU-fault and raw-PFN migration paths skip device-private folios carrying this flag. Mask the flag when retrieving the drm_pagemap_zdd pointer and preserve it when a device-private folio is split. Keeping the state on the physical folio also avoids depending on a virtual address that may change before a fault occurs. v2: - Replace the retired-PFN XArray with an embedded bitmap. (Matthew Brost) - Mark every base page covered by a migrated folio so retirement remains valid if the folio is later split. v3: - Store the migrated state in a low bit of zone_device_data instead of adding virtual-range and bitmap tracking to the ZDD. (Matthew Brost) - Mask the flag when retrieving the ZDD and preserve it when splitting a folio. - Drop the pre-existing fixes already covered by Matthew Brost's series: https://patchwork.freedesktop.org/series/171651/ v4: - Advance by the folio size only for migration entries marked with MIGRATE_PFN_COMPOUND. (Sashiko) v5: - Simplify ZDD flag updates and folio iteration. (Matthew Brost) - Skip retired device-private folios in the CPU-fault path. (Matthew Brost) - Preserve flag bits while taking a new ZDD reference for split folios. v6: - Restore MIGRATE_PFN_COMPOUND-aware stepping so non-compound migration entries are processed one at a time. (Sashiko) - Drop the pre-existing fixes already covered by Matthew Brost's series: https://patchwork.freedesktop.org/series/171651/ The lockup was observed as: [10109.860465] watchdog: BUG: soft lockup - CPU#9 stuck for 26s! [kworker/u65:5:6557] [10109.860524] Tainted: [S]=CPU_OUT_OF_SPEC, [O]=OOT_MODULE [10109.860524] Hardware name: ASUS System Product Name/PRIME Z790-P WIFI, BIOS 0812 02/24/2023 [10109.860525] Workqueue: xe_page_fault_work_queue xe_pagefault_queue_work [xe] [10109.860644] RIP: 0010:_raw_spin_unlock_irqrestore+0x57/0x80 [10109.860655] Call Trace: [10109.860655] [10109.860657] folio_lruvec_lock_irqsave+0x216/0x220 [10109.860661] ? __pfx_lru_add+0x10/0x10 [10109.860665] folio_batch_move_lru+0xc8/0x450 [10109.860670] ? lock_acquire+0xc4/0x2d0 [10109.860674] ? __folio_batch_add_and_move+0x60/0x2e0 [10109.860677] ? folio_migrate_mapping+0xa6/0x110 [10109.860679] ? folio_migrate_flags+0x13b/0x1b0 [10109.860681] ? __pfx_lru_add+0x10/0x10 [10109.860683] __folio_batch_add_and_move+0xe7/0x2e0 [10109.860685] ? dma_iova_try_alloc+0xb0/0x140 [10109.860689] folio_add_lru+0x64/0x80 [10109.860691] __migrate_device_finalize+0x12c/0x270 [10109.860695] migrate_device_finalize+0x10/0x20 [10109.860698] drm_pagemap_evict_to_ram+0x185/0x370 [drm_gpusvm_helper] [10109.860704] ? drm_pagemap_evict_to_ram+0x96/0x370 [drm_gpusvm_helper] [10109.860709] xe_svm_bo_evict+0x15/0x20 [xe] [10109.860819] ? xe_svm_bo_evict+0x15/0x20 [xe] [10109.860921] xe_bo_move+0x107e/0x1570 [xe] [10109.860992] ? xe_ttm_tt_create+0x168/0x340 [xe] [10109.861059] ? __up_read+0x98/0x2b0 [10109.861061] ? lock_is_held_type+0xa3/0x130 [10109.861067] ttm_bo_handle_move_mem+0xe8/0x1e0 [ttm] [10109.861075] ttm_bo_evict+0x141/0x1c0 [ttm] [10109.861081] ttm_bo_evict_cb+0x9f/0x100 [ttm] [10109.861086] ttm_lru_walk_for_evict+0x84/0x190 [ttm] [10109.861091] ? xe_ttm_vram_mgr_new+0x258/0x3a0 [xe] [10109.861198] ttm_bo_alloc_resource+0x219/0x750 [ttm] [10109.861203] ? ttm_bo_alloc_resource+0xa9/0x750 [ttm] [10109.861208] ? lock_acquire+0xc4/0x2d0 [10109.861214] ttm_bo_validate+0x94/0x1c0 [ttm] [10109.861218] ? ww_mutex_trylock+0x19d/0x3d0 [10109.861219] ? _raw_write_unlock+0x22/0x50 [10109.861223] ttm_bo_init_reserved+0x17d/0x1f0 [ttm] [10109.861228] xe_bo_init_locked+0x20a/0x620 [xe] [10109.861294] ? __pfx_xe_ttm_bo_destroy+0x10/0x10 [xe] [10109.861359] ? mark_held_locks+0x46/0x90 [10109.861361] ? __create_object+0x68/0xc0 [10109.861366] __xe_bo_create_locked+0x384/0xa20 [xe] [10109.861432] ? lock_acquire+0xc4/0x2d0 [10109.861434] ? xe_drm_pagemap_populate_mm+0xd3/0x340 [xe] [10109.861542] xe_bo_create_locked+0x23/0x40 [xe] [10109.861609] xe_drm_pagemap_populate_mm+0x12e/0x340 [xe] [10109.861707] ? __lock_acquire+0x43e/0x2930 [10109.861716] drm_pagemap_populate_mm+0x74/0xe0 [drm_gpusvm_helper] [10109.861720] xe_svm_alloc_vram+0xb5/0x2c0 [xe] [10109.861817] ? seqcount_lockdep_reader_access.constprop.0+0x9f/0xc0 [10109.861819] ? ktime_get+0x23/0x130 [10109.861821] ? trace_hardirqs_on+0x22/0xe0 [10109.861823] ? seqcount_lockdep_reader_access.constprop.0+0x9f/0xc0 [10109.861826] __xe_svm_handle_pagefault+0x77d/0xbf0 [xe] [10109.861924] ? rwsem_down_write_slowpath+0x43a/0x9a0 [10109.861926] ? _raw_spin_unlock_irq+0x27/0x70 [10109.861928] ? rwsem_down_write_slowpath+0x43a/0x9a0 [10109.861929] ? trace_hardirqs_on+0x22/0xe0 [10109.861931] ? _raw_spin_unlock_irq+0x27/0x70 [10109.861933] ? rwsem_down_write_slowpath+0x459/0x9a0 [10109.861937] xe_svm_handle_pagefault+0x3d/0xb0 [xe] [10109.862030] xe_pagefault_queue_work+0x1a9/0x520 [xe] [10109.862122] process_one_work+0x239/0x730 [10109.862127] worker_thread+0x200/0x3f0 [10109.862130] ? __pfx_worker_thread+0x10/0x10 [10109.862132] kthread+0x10d/0x150 [10109.862133] ? __pfx_kthread+0x10/0x10 [10109.862135] ret_from_fork+0x3bd/0x470 [10109.862138] ? __pfx_kthread+0x10/0x10 [10109.862140] ret_from_fork_asm+0x1a/0x30 [10109.862146] Fixes: 99624bdff867 ("drm/gpusvm: Add support for GPU Shared Virtual Memory") Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: Matthew Brost Cc: Thomas Zimmermann Cc: David Airlie Cc: Simona Vetter Cc: Thomas Hellström Cc: Himal Prasad Ghimiray Assisted-by: Claude:claude-opus-4-8 Suggested-by: Matthew Brost Signed-off-by: Arvind Yadav Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260810092845.2776097-1-arvind.yadav@intel.com --- drivers/gpu/drm/drm_pagemap.c | 127 ++++++++++++++++++++++++++++++++-- include/drm/drm_pagemap.h | 8 ++- 2 files changed, 129 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 892b325fa99b..c5906f153ada 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -1102,12 +1102,117 @@ void drm_pagemap_put(struct drm_pagemap *dpagemap) } EXPORT_SYMBOL(drm_pagemap_put); +/** + * drm_pagemap_page_get_flags() - Read flags from a device-private folio + * @page: Pointer to a page of the device-private folio + * + * Return: The DRM_PAGEMAP_ZDD_FLAG_* bits encoded in zone_device_data. + */ +static unsigned long drm_pagemap_page_get_flags(struct page *page) +{ + struct folio *folio = page_folio(page); + + return (unsigned long)folio_zone_device_data(folio) & + DRM_PAGEMAP_ZDD_FLAG_MASK; +} + +/** + * drm_pagemap_page_set_flags() - Set flags on a device-private folio + * @page: Pointer to a page of the device-private folio + * @flags: DRM_PAGEMAP_ZDD_FLAG_* bits to set + * + * Preserve any flags already encoded alongside the ZDD pointer. + */ +static void drm_pagemap_page_set_flags(struct page *page, + unsigned long flags) +{ + struct folio *folio = page_folio(page); + unsigned long old; + + if (WARN_ON_ONCE(flags & ~DRM_PAGEMAP_ZDD_FLAG_MASK)) + return; + + old = (unsigned long)folio_zone_device_data(folio); + folio_set_zone_device_data(folio, (void *)(old | flags)); +} + +/** + * drm_pagemap_retire_migrated_pages() - Record migrated device-private folios + * @src_pfns: source array after migrate_vma_pages() or migrate_device_pages() + * @npages: number of entries in @src_pfns + * + * Flag device-private folios successfully migrated to RAM before finalize + * unlocks the sources. The migrated state is stored in the physical folio, so + * it survives later folio splits and subsequent migrations can skip it. + */ +static void drm_pagemap_retire_migrated_pages(unsigned long *src_pfns, + unsigned long npages) +{ + unsigned long i = 0; + + while (i < npages) { + struct page *page = migrate_pfn_to_page(src_pfns[i]); + unsigned long nr = 1; + + if (!page) { + i++; + continue; + } + + if (src_pfns[i] & MIGRATE_PFN_COMPOUND) + nr = folio_nr_pages(page_folio(page)); + + if ((src_pfns[i] & MIGRATE_PFN_MIGRATE) && + is_device_private_page(page)) + drm_pagemap_page_set_flags(page, + DRM_PAGEMAP_ZDD_FLAG_MIGRATED); + + i += nr; + } +} + +/** + * drm_pagemap_skip_retired_pages() - Skip retired device-private folios + * @src_pfns: MIGRATE_PFN-encoded source array + * @npages: number of entries in @src_pfns + * + * Skip source folios already migrated to RAM, identified by the migrated flag + * stored in the physical folio's zone_device_data. + */ +static void drm_pagemap_skip_retired_pages(unsigned long *src_pfns, + unsigned long npages) +{ + unsigned long i = 0; + + while (i < npages) { + struct page *page = migrate_pfn_to_page(src_pfns[i]); + unsigned long nr = 1; + + if (!page) { + i++; + continue; + } + + if (src_pfns[i] & MIGRATE_PFN_COMPOUND) + nr = folio_nr_pages(page_folio(page)); + + if ((src_pfns[i] & MIGRATE_PFN_MIGRATE) && + is_device_private_page(page) && + (drm_pagemap_page_get_flags(page) & + DRM_PAGEMAP_ZDD_FLAG_MIGRATED)) + src_pfns[i] &= ~MIGRATE_PFN_MIGRATE; + + i += nr; + } +} + /** * drm_pagemap_evict_to_ram() - Evict GPU SVM range to RAM * @devmem_allocation: Pointer to the device memory allocation * - * Similar to __drm_pagemap_migrate_to_ram but does not require mmap lock and - * migration done via migrate_device_* functions. + * Similar to __drm_pagemap_migrate_to_ram(), but uses the + * migrate_device_* helpers and does not require the mmap lock. + * Device-private PFNs already migrated to RAM by either path are skipped. * * Return: 0 on success, negative error code on failure. */ @@ -1148,6 +1253,8 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) if (err) goto err_free; + drm_pagemap_skip_retired_pages(src, npages); + err = drm_pagemap_migrate_populate_ram_pfn(NULL, NULL, npages, &mpages, src, dst, 0); if (err || !mpages) @@ -1178,6 +1285,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) if (err) drm_pagemap_migration_unlock_put_pages(npages, dst); migrate_device_pages(src, dst, npages); + drm_pagemap_retire_migrated_pages(src, npages); migrate_device_finalize(src, dst, npages); drm_pagemap_migrate_unmap_pages(devmem_allocation->dev, pagemap_addr, dst, npages, DMA_FROM_DEVICE, &state); @@ -1275,13 +1383,15 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, if (!migrate.cpages) goto err_free; + drm_pagemap_skip_retired_pages(migrate.src, npages); + ops = zdd->devmem_allocation->ops; dev = zdd->devmem_allocation->dev; err = drm_pagemap_migrate_populate_ram_pfn(vas, page, npages, &mpages, migrate.src, migrate.dst, start); - if (err) + if (err || !mpages) goto err_finalize; err = drm_pagemap_migrate_map_system_pages(dev, pagemap_addr, @@ -1308,6 +1418,7 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, if (err) drm_pagemap_migration_unlock_put_pages(npages, migrate.dst); migrate_vma_pages(&migrate); + drm_pagemap_retire_migrated_pages(migrate.src, npages); migrate_vma_finalize(&migrate); if (dev) drm_pagemap_migrate_unmap_pages(dev, pagemap_addr, migrate.dst, @@ -1360,13 +1471,19 @@ static vm_fault_t drm_pagemap_migrate_to_ram(struct vm_fault *vmf) static void drm_pagemap_folio_split(struct folio *orig_folio, struct folio *new_folio) { struct drm_pagemap_zdd *zdd; + unsigned long orig_data, new_data; if (!new_folio) return; new_folio->pgmap = orig_folio->pgmap; - zdd = folio_zone_device_data(orig_folio); - folio_set_zone_device_data(new_folio, drm_pagemap_zdd_get(zdd)); + + orig_data = (unsigned long)folio_zone_device_data(orig_folio); + zdd = (struct drm_pagemap_zdd *)(orig_data & ~DRM_PAGEMAP_ZDD_FLAG_MASK); + + new_data = (unsigned long)drm_pagemap_zdd_get(zdd); + new_data |= orig_data & DRM_PAGEMAP_ZDD_FLAG_MASK; + folio_set_zone_device_data(new_folio, (void *)new_data); } static const struct dev_pagemap_ops drm_pagemap_pagemap_ops = { diff --git a/include/drm/drm_pagemap.h b/include/drm/drm_pagemap.h index 95eb4b66b057..ebbd3b0ddf36 100644 --- a/include/drm/drm_pagemap.h +++ b/include/drm/drm_pagemap.h @@ -2,6 +2,7 @@ #ifndef _DRM_PAGEMAP_H_ #define _DRM_PAGEMAP_H_ +#include #include #include #include @@ -339,6 +340,9 @@ struct drm_pagemap_migrate_details { #if IS_ENABLED(CONFIG_ZONE_DEVICE) +#define DRM_PAGEMAP_ZDD_FLAG_MIGRATED BIT(0) +#define DRM_PAGEMAP_ZDD_FLAG_MASK DRM_PAGEMAP_ZDD_FLAG_MIGRATED + int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation, struct mm_struct *mm, unsigned long start, unsigned long end, @@ -373,7 +377,9 @@ static inline struct drm_pagemap_zdd *drm_pagemap_page_zone_device_data(struct p { struct folio *folio = page_folio(page); - return folio_zone_device_data(folio); + return (struct drm_pagemap_zdd *) + ((unsigned long)folio_zone_device_data(folio) & + ~DRM_PAGEMAP_ZDD_FLAG_MASK); } #else From 8eae39cd0adf28ba81a46090b10484cf402c0ac8 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Tue, 28 Jul 2026 14:33:04 +0530 Subject: [PATCH 38/53] drm/pagemap: Reset migration page count on eviction retry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_pagemap_evict_to_ram() may retry eviction, but mpages retains the count from the previous attempt. A retry can therefore continue to the copy path even when no RAM pages were populated. Reset mpages at the retry label so it reflects only the current attempt. Fixes: 99624bdff867 ("drm/gpusvm: Add support for GPU Shared Virtual Memory") Cc: Matthew Brost Cc: Thomas Hellström Cc: Himal Prasad Ghimiray Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: Thomas Zimmermann Cc: David Airlie Cc: Simona Vetter Signed-off-by: Arvind Yadav Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260728090304.1264759-1-arvind.yadav@intel.com --- drivers/gpu/drm/drm_pagemap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index c5906f153ada..097a900cf55d 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -1220,7 +1220,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) { const struct drm_pagemap_devmem_ops *ops = devmem_allocation->ops; struct drm_pagemap_iova_state state = {}; - unsigned long npages, mpages = 0; + unsigned long npages, mpages; struct page **pages; unsigned long *src, *dst; struct drm_pagemap_addr *pagemap_addr; @@ -1231,6 +1231,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) npages = devmem_allocation->size >> PAGE_SHIFT; retry: + mpages = 0; if (!mmget_not_zero(devmem_allocation->mm)) return -EFAULT; From 412a6ceb56d501ef2f8202e26ab4b5d4dfbca566 Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Tue, 11 Aug 2026 16:46:28 +0800 Subject: [PATCH 39/53] drm/nouveau/uvmm: fix NULL deref unwinding an OP_MAP_SPARSE op Each bind_job_op is zeroed by kzalloc_obj() in bind_job_op_from_uop(), and the OP_MAP_SPARSE case in nouveau_uvmm_bind_job_submit() only creates a region, so op->ops stays NULL for a successfully processed sparse map. If a later op in the same job fails, the reverse unwind loop revisits that op and calls drm_gpuva_ops_free(&uvmm->base, op->ops) unconditionally. drm_gpuva_ops_free() dereferences its argument right away (list_for_each_entry_safe on &ops->list), so a NULL op->ops oopses. The path is reachable by any render-node fd holder, since NOUVEAU_VM_BIND is DRM_RENDER_ALLOW. Guard the free with IS_ERR_OR_NULL(), as nouveau_uvmm_bind_job_cleanup() already does for the identical free. Fixes: b88baab82871 ("drm/nouveau: implement new VM_BIND uAPI") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Reviewed-by: Lyude Paul Link: https://patch.msgid.link/20260811-nouveau-uvmm-vmbind-fixes-v2-1-aaee4b395d04@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_uvmm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index f5e4756b4de4..19e758a20c24 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -1489,7 +1489,8 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job, break; } - drm_gpuva_ops_free(&uvmm->base, op->ops); + if (!IS_ERR_OR_NULL(op->ops)) + drm_gpuva_ops_free(&uvmm->base, op->ops); op->ops = NULL; op->reg = NULL; } From ccf930812f23b8259ef64fd3394d53b093e4651a Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Tue, 11 Aug 2026 16:46:29 +0800 Subject: [PATCH 40/53] drm/nouveau/uvmm: fix premature region free on failed OP_UNMAP_SPARSE In nouveau_uvmm_bind_job_submit()'s OP_UNMAP_SPARSE arm, op->reg is set from nouveau_uvma_region_find(), which only looks the region up and takes no reference; a region's sole reference is its membership in uvmm->region_mt. Two failure paths leave op->reg set: the -ENOENT check when the region is busy, and the drm_gpuvm_sm_unmap_ops_create() failure. The sibling nouveau_uvmm_sm_unmap_prepare() failure just below clears op->reg; these two do not. unwind_continue steps back one op, so the failing op is skipped by the unwind loop and its op->reg stays set. nouveau_uvmm_bind_job_cleanup() then enters its if (op->reg) branch and calls nouveau_uvma_region_remove() and nouveau_uvma_region_put() on it, dropping the tree's sole reference and freeing a region this job never created. The comment above the cleanup loop documents the broken invariant: op->reg must be NULL on submit failure. This frees a live region on an unrelated failure, reachable single-job when drm_gpuvm_sm_unmap_ops_create() returns -ENOMEM; if another job owns the same region, its cleanup then removes and puts the freed region, a use-after-free. Clear op->reg on both failure paths. Fixes: b88baab82871 ("drm/nouveau: implement new VM_BIND uAPI") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Reviewed-by: Lyude Paul Link: https://patch.msgid.link/20260811-nouveau-uvmm-vmbind-fixes-v2-2-aaee4b395d04@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_uvmm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index 19e758a20c24..d30ec3709e79 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -1319,6 +1319,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job, op->va.range); if (!op->reg || op->reg->dirty) { ret = -ENOENT; + op->reg = NULL; goto unwind_continue; } @@ -1327,6 +1328,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job, op->va.range); if (IS_ERR(op->ops)) { ret = PTR_ERR(op->ops); + op->reg = NULL; goto unwind_continue; } From 38a62306c4266bcb3cd89e33c7111ee33096ebb3 Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Tue, 11 Aug 2026 16:46:30 +0800 Subject: [PATCH 41/53] drm/nouveau/uvmm: clear the dirty flag when unwinding an OP_UNMAP_SPARSE A successful OP_UNMAP_SPARSE marks its region dirty with nouveau_uvma_region_dirty() and defers the teardown to nouveau_uvmm_bind_job_cleanup(); it does not remove the region from uvmm->region_mt. If a later op in the job fails, the unwind path never clears reg->dirty (set in one place, cleared nowhere) and sets op->reg = NULL, so cleanup skips the teardown. The region is left in the tree with dirty set and its completion never signalled. Later binds over that range then fail permanently -- -ENOENT or -EINVAL from the dirty checks, or an unkillable wait_for_completion() in bind_validate_region() -- for the lifetime of the uvmm. Clear reg->dirty when the unwind reverts the sparse unmap, restoring the region to the state it was found in. Fixes: b88baab82871 ("drm/nouveau: implement new VM_BIND uAPI") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Reviewed-by: Lyude Paul Link: https://patch.msgid.link/20260811-nouveau-uvmm-vmbind-fixes-v2-3-aaee4b395d04@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_uvmm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index d30ec3709e79..fc125fd44a9b 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -1475,6 +1475,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job, op->va.range); break; case OP_UNMAP_SPARSE: + op->reg->dirty = false; __nouveau_uvma_region_insert(uvmm, op->reg); nouveau_uvmm_sm_unmap_prepare_unwind(uvmm, &op->new, op->ops); From deced5fa01c5e9813384b6c176379e5baaf5ec10 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Tue, 25 Aug 2026 13:06:15 +1000 Subject: [PATCH 42/53] nouveau/instmem: handle iomapping already existing Turns out sashiko was right, and I should protect this properly Fixes: 34e27b90552a ("nouveau/instmem: use iomapping interface for instmem handling") Signed-off-by: Dave Airlie Link: https://patch.msgid.link/20260825030615.3464436-1-airlied@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c index f4489efc94a7..22b0fde6ba34 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c @@ -195,6 +195,9 @@ check_io_mapping(struct nv50_instmem *imem) { struct nvkm_device *device = imem->base.subdev.device; + if (imem->iomap.size) + return true; + return io_mapping_init_wc(&imem->iomap, device->func->resource_addr(device, NVKM_BAR2_INST), device->func->resource_size(device, NVKM_BAR2_INST)) != NULL; From caa1bc2a0a6ca19dcb90bbf88208b0fe2decd66f Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Tue, 11 Aug 2026 22:28:50 +0800 Subject: [PATCH 43/53] drm/nouveau/dmem: fix mismatched DMA unmap size for large folios Device-private THP migration maps migration buffers with page_size() and records that length in dma_info->size. For a compound folio page_size() is PAGE_SIZE << order, but two teardown sites still pass a literal PAGE_SIZE to dma_unmap_page(): - nouveau_dmem_migrate_to_ram() on the success path, and - nouveau_dmem_migrate_copy_one() on the copy-error path. For an order > 0 folio this unmaps less than was mapped, leaking the remainder of the IOMMU/IOVA mapping. The other unmap sites, in nouveau_dmem_migrate_chunk() and nouveau_dmem_evict_chunk(), already use the saved size; use it here too. Fixes: c32287471077 ("gpu/drm/nouveau: enable THP support for GPU memory migration") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Link: https://patch.msgid.link/20260811-b4-nouveau-dmem-thp-fixes-v1-1-2cdf9860af2a@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_dmem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_dmem.c b/drivers/gpu/drm/nouveau/nouveau_dmem.c index 9442ec6e1f6c..d2abee3efb9a 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dmem.c +++ b/drivers/gpu/drm/nouveau/nouveau_dmem.c @@ -267,7 +267,7 @@ static vm_fault_t nouveau_dmem_migrate_to_ram(struct vm_fault *vmf) nouveau_fence_new(&fence, dmem->migrate.chan); migrate_vma_pages(&args); nouveau_dmem_fence_done(&fence); - dma_unmap_page(drm->dev->dev, dma_info.dma_addr, PAGE_SIZE, + dma_unmap_page(drm->dev->dev, dma_info.dma_addr, dma_info.size, DMA_BIDIRECTIONAL); done: migrate_vma_finalize(&args); @@ -772,7 +772,7 @@ static unsigned long nouveau_dmem_migrate_copy_one(struct nouveau_drm *drm, return mpfn; out_dma_unmap: - dma_unmap_page(dev, dma_info->dma_addr, PAGE_SIZE, DMA_BIDIRECTIONAL); + dma_unmap_page(dev, dma_info->dma_addr, dma_info->size, DMA_BIDIRECTIONAL); out_free_page: nouveau_dmem_page_free_locked(drm, dpage); out: From c2256c044a1df39c8aad4dd2d6f709b2533e2d7a Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Tue, 11 Aug 2026 22:28:51 +0800 Subject: [PATCH 44/53] drm/nouveau/dmem: fix callocated underflow on large folio split nouveau_dmem_folio_free() drops chunk->callocated once per freed folio, while a large (compound) device-private folio is only counted once when it is allocated. When such a folio is split, the mm core invokes ->folio_split() (nouveau_dmem_folio_split()) once for each new sub-folio, but the hook only fixes up the sub-folio metadata and leaves chunk->callocated unchanged. Each resulting sub-folio is later freed separately, so after a split the single allocation (+1) is met by N frees (-N), leaving chunk->callocated short by N-1. On the first split/free cycle it underflows: WARN_ON(!chunk->callocated) fires, the unsigned counter wraps and never returns to zero, so the chunk can no longer be reclaimed (nouveau_dmem_fini() also warns on the leaked count). Account for the new sub-folio in the split hook, under the same lock as nouveau_dmem_folio_free(), so the count stays balanced. Fixes: c32287471077 ("gpu/drm/nouveau: enable THP support for GPU memory migration") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Reviewed-by: Lyude Paul Link: https://patch.msgid.link/20260811-b4-nouveau-dmem-thp-fixes-v1-2-2cdf9860af2a@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_dmem.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_dmem.c b/drivers/gpu/drm/nouveau/nouveau_dmem.c index d2abee3efb9a..ad4570c50be7 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dmem.c +++ b/drivers/gpu/drm/nouveau/nouveau_dmem.c @@ -279,11 +279,25 @@ static vm_fault_t nouveau_dmem_migrate_to_ram(struct vm_fault *vmf) static void nouveau_dmem_folio_split(struct folio *head, struct folio *tail) { + struct nouveau_dmem_chunk *chunk; + struct nouveau_dmem *dmem; + if (tail == NULL) return; tail->pgmap = head->pgmap; tail->mapping = head->mapping; folio_set_zone_device_data(tail, folio_zone_device_data(head)); + + /* + * The split hands out a new independently-freeable folio that will + * later be released via nouveau_dmem_folio_free(); account for it so + * chunk->callocated stays balanced. + */ + chunk = nouveau_page_to_chunk(&head->page); + dmem = chunk->drm->dmem; + spin_lock(&dmem->lock); + chunk->callocated++; + spin_unlock(&dmem->lock); } static const struct dev_pagemap_ops nouveau_dmem_pagemap_ops = { From 0ba8e0f90039da68342febf613019f4a68d86620 Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Thu, 20 Aug 2026 01:44:57 +0300 Subject: [PATCH 45/53] accel/amdxdna: refuse to flush an imported BO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SYNC_BO clflushes an imported BO's scatterlist. An importer may not do that: the memory belongs to the exporter, and dma-buf gives the importer no interface to ask for maintenance on it. Refuse the request instead. is_import_bo() is (obj)->attach, which covers more than foreign buffers. A userptr BO arrives through a ubuf, and on a carveout device every share BO and the device heap arrive through a cbuf, so SYNC_BO answers -EOPNOTSUPP for those too, including the AMDXDNA_BO_DEV path that flushes through its heap. Only the ubuf case gives up maintenance it was getting: on a 64 MiB userptr BO a 4 KiB sync and a full sync both cost 659 us, this arm having ignored the range. amdxdna_cbuf_map() fills in only the DMA address and length, so drm_clflush_sg() already walks zero pages on carveout memory. Userspace maintains these through the mapping it already holds, as XRT's buffer::sync() does unless it is told to sync through the driver. Fixes: dbc8fd7a03cb ("accel/amdxdna: Add expandable device heap support") Reported-by: Christian König Link: https://lore.kernel.org/dri-devel/a505f9e5-b416-43e9-934d-c5c29b8a70e9@amd.com/ Suggested-by: Lizhi Hou Signed-off-by: Taimuraz Kaitmazov Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260819224458.257346-5-taimuraz@kaitmazov.com --- drivers/accel/amdxdna/amdxdna_gem.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index d18de7eb7af4..4b0d58d0329b 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -1246,6 +1246,9 @@ static int amdxdna_flush_bo(struct amdxdna_gem_obj *abo, u64 offset, u64 size) { u64 end; + if (is_import_bo(abo)) + return -EOPNOTSUPP; + if (offset >= abo->mem.size) return -EINVAL; @@ -1256,9 +1259,7 @@ static int amdxdna_flush_bo(struct amdxdna_gem_obj *abo, u64 offset, u64 size) if (!size) return 0; - if (is_import_bo(abo)) - drm_clflush_sg(abo->base.sgt); - else if (amdxdna_gem_vmap(abo)) + if (amdxdna_gem_vmap(abo)) drm_clflush_virt_range(amdxdna_gem_vmap(abo) + offset, size); else if (abo->base.pages) drm_clflush_pages(abo->base.pages, abo->mem.size >> PAGE_SHIFT); From 774b73428e6eabb4f0382aeeb76e569c7b106a29 Mon Sep 17 00:00:00 2001 From: Faith Ekstrand Date: Fri, 21 Aug 2026 23:42:59 -0500 Subject: [PATCH 46/53] drm/nouveau: Use write-combined maps for coherent On Tegra devices, uncached maps translate to device memory, causing unaligned accesses by userspace resulting in a SIGBUS. Instead, use write-combined maps to ensure proper access. This would also affect discrete cards on any Arm device. It was determined that discrete cards regardless of cpu arch should use write-combined maps for coherent anyways. Thus this change is made for all gpu types. Cc: stable@vger.kernel.org Signed-off-by: Faith Ekstrand Co-developed-by: Aaron Kling Signed-off-by: Aaron Kling Fixes: 1b4ea4c5980f ("drm/ttm: set the tt caching state at creation time") Link: https://patch.msgid.link/20260821-tegra-coherent-wc-v2-1-2b1ddb67bf18@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_sgdma.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_sgdma.c b/drivers/gpu/drm/nouveau/nouveau_sgdma.c index fa3b4ebf38a8..2bd0376193ae 100644 --- a/drivers/gpu/drm/nouveau/nouveau_sgdma.c +++ b/drivers/gpu/drm/nouveau/nouveau_sgdma.c @@ -72,9 +72,7 @@ nouveau_sgdma_create_ttm(struct ttm_buffer_object *bo, uint32_t page_flags) struct nouveau_sgdma_be *nvbe; enum ttm_caching caching; - if (nvbo->force_coherent) - caching = ttm_uncached; - else if (drm->agp.bridge) + if (nvbo->force_coherent || drm->agp.bridge) caching = ttm_write_combined; else caching = ttm_cached; From c3080b58d81d3699cbf4dfd5ba860630fca96f4a Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Wed, 26 Aug 2026 12:37:05 +0200 Subject: [PATCH 47/53] drm/atomic-state-helper: set pixel_blend_mode to prop default on reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In __drm_atomic_helper_plane_state_init(), pixel_blend_mode is always reset to DRM_MODE_BLEND_PREMULTI. That was consistent while drm_plane_create_blend_mode_property() required PREMULTI in the supported modes, but it now falls back to COVERAGE or PIXEL_NONE when the driver doesn't support PREMULTI. The hardcoded default may therefore not be a blend mode the hardware can do, nor one the property advertises. Initialize pixel_blend_mode from the blend mode property default instead, keeping DRM_MODE_BLEND_PREMULTI for planes without the property. Fixes: 9813e158d13d ("drm/drm_blend: allow blend mode property without PREMULTI") Tested-by: Mikhail Gavrilov Tested-by: Dan Wheeler Reviewed-by: Timur Kristóf Reviewed-by: Alex Hung Reviewed-by: Leandro Ribeiro Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260826104143.39077-2-mwen@igalia.com --- drivers/gpu/drm/drm_atomic_state_helper.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index d90d1d7c9cf9..a2ef272e9f27 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -278,7 +278,14 @@ void __drm_atomic_helper_plane_state_init(struct drm_plane_state *plane_state, plane_state->rotation = DRM_MODE_ROTATE_0; plane_state->alpha = DRM_BLEND_ALPHA_OPAQUE; + plane_state->pixel_blend_mode = DRM_MODE_BLEND_PREMULTI; + if (plane->blend_mode_property) { + if (!drm_object_property_get_default_value(&plane->base, + plane->blend_mode_property, + &val)) + plane_state->pixel_blend_mode = val; + } if (plane->color_encoding_property) { if (!drm_object_property_get_default_value(&plane->base, From f0c75da0a6b4084e00cd6faefebcc976ffaccf52 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Wed, 26 Aug 2026 12:37:06 +0200 Subject: [PATCH 48/53] drm/amd/display: fix missing blend-mode-prop warning for DCN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validate_blend_mode_for_alpha_formats() warns when a plane supports formats with alpha but doesn't expose the blend mode property. Fix this by adding the same overlay plane blend modes to primary plane, since they are all universal planes in DCN-generation. Cursor planes support ARGB8888 format and CURSOR_MODE_COLOR_PRE_MULTIPLIED_ALPHA is set by default (other color formats are not implemented), so only expose support to PREMULTI, which is the default blend mode on DRM. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Tested-by: Mikhail Gavrilov Tested-by: Dan Wheeler Reviewed-by: Timur Kristóf Reviewed-by: Alex Hung Reviewed-by: Leandro Ribeiro Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260826104143.39077-3-mwen@igalia.com --- .../amd/display/amdgpu_dm/amdgpu_dm_plane.c | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 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 824ef3ce5de0..423e3cd7b9c9 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 @@ -2208,16 +2208,31 @@ int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm, if (res) return res; - if (plane->type == DRM_PLANE_TYPE_OVERLAY && - plane_cap && plane_cap->per_pixel_alpha) { + /* TODO: Check which blend modes are supported in DCE-generation + * planes, i.e. DC_PLANE_TYPE_DCE_RGB/UNDERLAY and expose blend mode + * property accordingly. + */ + if ((plane->type == DRM_PLANE_TYPE_OVERLAY || + plane->type == DRM_PLANE_TYPE_PRIMARY) && + plane_cap && plane_cap->per_pixel_alpha && + plane_cap->type == DC_PLANE_TYPE_DCN_UNIVERSAL) { unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) | BIT(DRM_MODE_BLEND_PREMULTI) | BIT(DRM_MODE_BLEND_COVERAGE); - drm_plane_create_alpha_property(plane); drm_plane_create_blend_mode_property(plane, blend_caps); + + if (plane->type == DRM_PLANE_TYPE_OVERLAY) + drm_plane_create_alpha_property(plane); } + /* Cursor color format is set to CURSOR_MODE_COLOR_PRE_MULTIPLIED_ALPHA + * by default, so only advertise DRM_MODE_BLEND_PREMULTI blend mode for + * this type of plane. + */ + if (plane->type == DRM_PLANE_TYPE_CURSOR) + drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_PREMULTI)); + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { /* * Allow OVERLAY planes to be used as underlays by assigning an From 332ad707e38fb82dd998b4d0782c15579e5784f1 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Wed, 26 Aug 2026 12:37:07 +0200 Subject: [PATCH 49/53] drm/amd/display: advertise PIXEL_NONE and PREMULTI blend mode for DCE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DCE can support PREMULTI and COVERAGE blend mode depending on its generation, however current driver implementation either doesn't expose more than primary and cursor plane, or doesn't program registers for any blend mode other than PIXEL_NONE. To fix the missing-blend-mode-prop warning according to current DCE plane caps, create blend mode property with PIXEL_NONE and PREMULTI for primary planes. As long as the background is black and there is no overlay plane, PIXEL_NONE and PREMULTI are equivalent, and PREMULTI has been the mandatory/default mode for years, so keep it to avoid regressions. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Tested-by: Viktor Jägersküpper Tested-by: Dan Wheeler #v3 Reviewed-by: Timur Kristóf Reviewed-by: Alex Hung Reviewed-by: Leandro Ribeiro #v2 Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260826104143.39077-4-mwen@igalia.com --- .../amd/display/amdgpu_dm/amdgpu_dm_plane.c | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 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 423e3cd7b9c9..e13b96358208 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 @@ -2208,14 +2208,24 @@ int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm, if (res) return res; - /* TODO: Check which blend modes are supported in DCE-generation - * planes, i.e. DC_PLANE_TYPE_DCE_RGB/UNDERLAY and expose blend mode - * property accordingly. + /* Blend mode support varies on DCE generations according to HW caps + * and number of planes per CRTC. However, as current driver + * implementation only creates one primary and one cursor plane per + * CRTC for DCE (overlay is only created if + * DC_PLANE_TYPE_DCN_UNIVERSAL), the primary plane blend mode is + * ignored across DCE versions. Keep PREMULTI to avoid uAPI + * regressions: it was the default/mandatory mode for many years and, + * with no overlay plane, primary composes on top of a black + * background, where PREMULTI and PIXEL_NONE are equivalent. */ - if ((plane->type == DRM_PLANE_TYPE_OVERLAY || - plane->type == DRM_PLANE_TYPE_PRIMARY) && - plane_cap && plane_cap->per_pixel_alpha && - plane_cap->type == DC_PLANE_TYPE_DCN_UNIVERSAL) { + if (plane_cap && plane_cap->type != DC_PLANE_TYPE_DCN_UNIVERSAL) { + unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI); + + drm_plane_create_blend_mode_property(plane, blend_caps); + } else if ((plane->type == DRM_PLANE_TYPE_OVERLAY || + plane->type == DRM_PLANE_TYPE_PRIMARY) && + plane_cap && plane_cap->per_pixel_alpha) { unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) | BIT(DRM_MODE_BLEND_PREMULTI) | BIT(DRM_MODE_BLEND_COVERAGE); From 9e6372ec2a3990662ae0a67f56ac0aee19848d5b Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Tue, 1 Sep 2026 23:35:03 -0700 Subject: [PATCH 50/53] drm/pagemap: dma-unmap pages before handling migration errors drm_pagemap_migrate_unmap_pages() relies on the pages array to determine which pages require DMA unmapping. However, drm_pagemap_migration_unlock_put_pages() clears the array as part of its cleanup, leaving drm_pagemap_migrate_unmap_pages() with no valid page information if it is called afterward. Call drm_pagemap_migrate_unmap_pages() before drm_pagemap_migration_unlock_put_pages() so the pages array remains valid during DMA unmapping. Reported-by: Sashiko Fixes: f86ad0ed620c ("drm/gpusvm, drm/pagemap: Move migration functionality to drm_pagemap") Cc: stable@vger.kernel.org Signed-off-by: Matthew Brost Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260902063504.3024362-1-matthew.brost@intel.com --- drivers/gpu/drm/drm_pagemap.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 097a900cf55d..6aa745682270 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -1283,13 +1283,13 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) goto err_finalize; err_finalize: + drm_pagemap_migrate_unmap_pages(devmem_allocation->dev, pagemap_addr, dst, npages, + DMA_FROM_DEVICE, &state); if (err) drm_pagemap_migration_unlock_put_pages(npages, dst); migrate_device_pages(src, dst, npages); drm_pagemap_retire_migrated_pages(src, npages); migrate_device_finalize(src, dst, npages); - drm_pagemap_migrate_unmap_pages(devmem_allocation->dev, pagemap_addr, dst, npages, - DMA_FROM_DEVICE, &state); err_free: kvfree(buf); @@ -1416,15 +1416,15 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, goto err_finalize; err_finalize: + if (dev) + drm_pagemap_migrate_unmap_pages(dev, pagemap_addr, migrate.dst, + npages, DMA_FROM_DEVICE, + &state); if (err) drm_pagemap_migration_unlock_put_pages(npages, migrate.dst); migrate_vma_pages(&migrate); drm_pagemap_retire_migrated_pages(migrate.src, npages); migrate_vma_finalize(&migrate); - if (dev) - drm_pagemap_migrate_unmap_pages(dev, pagemap_addr, migrate.dst, - npages, DMA_FROM_DEVICE, - &state); err_free: kvfree(buf); err_out: From df72e55e754c8d449321ddddad19a8bd3cb8d032 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Tue, 1 Sep 2026 23:35:04 -0700 Subject: [PATCH 51/53] drm/pagemap: Fix folio allocation fallback and use-after-put drm_pagemap_migrate_populate_ram_pfn() had two issues when populating RAM PFNs with higher-order folios: 1. The higher-order vma_alloc_folio()/folio_alloc() calls did not pass __GFP_NOWARN, so a THP allocation failure under memory pressure would spam the kernel log, and there was no fallback path despite a TODO comment stating one was needed. Add __GFP_NOWARN to the higher-order allocation and, on failure, fall back to order-0 allocations for the entire range originally covered by the failed higher-order allocation, leaving MIGRATE_PFN_COMPOUND unset for those PFNs. 2. In the free_pages error path, order was computed via folio_order(page_folio(page)) *after* put_page(page) had already dropped the reference, resulting in a use-after-free/put when that was the last reference on the page. Compute order before releasing the page. Introducing the fallback in 1. also requires the source page array handed to ->copy_to_ram() to be built differently. Both callers only populated the entry at the head of each source folio, relying on the copy callback to derive the rest of the folio from the order recorded in the matching drm_pagemap_addr. Once the destination has been demoted to order-0 folios the drm_pagemap_addr entries are per-page, so a source page is needed for every one of them; leaving them NULL makes the copy callback stop after the first page and the remainder of the range is never copied. The source folio is only split later, by migrate_vma_pages() / migrate_device_pages(), so its order cannot be used to detect the demotion - test the destination for MIGRATE_PFN_COMPOUND instead. Factor the array population out into drm_pagemap_migrate_populate_src_pages() and use it from both drm_pagemap_evict_to_ram() and __drm_pagemap_migrate_to_ram(). Fixes: ddeda6136038 ("drm/pagemap: Allocate folios when possible") Cc: stable@vger.kernel.org Assisted-by: GitHub_Copilot:claude-opus-5 Signed-off-by: Matthew Brost Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260902063504.3024362-2-matthew.brost@intel.com --- drivers/gpu/drm/drm_pagemap.c | 128 +++++++++++++++++++++++++++------- 1 file changed, 103 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 6aa745682270..a0546955d0b9 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -383,6 +383,58 @@ drm_pagemap_migrate_map_system_pages(struct device *dev, return 0; } +/** + * drm_pagemap_migrate_populate_src_pages() - Populate the source page array + * @pages: Array of source pages to populate + * @src_mpfn: Source array of migrate PFNs + * @dst_mpfn: Destination array of migrate PFNs + * @npages: Number of pages in the arrays + * + * Populate @pages with the device pages the copy callback is to read from. + * + * Entries are normally only populated at the head of each source folio, with + * the copy callback deriving the rest of the folio from the order recorded in + * the corresponding drm_pagemap_addr. That does not work where + * drm_pagemap_migrate_populate_ram_pfn() had to demote a higher-order source + * folio to order-0 destination folios: the drm_pagemap_addr entries are then + * per-page, and the copy callback needs a source page for each of them. + * Populate every entry for those ranges. + * + * Note that the source folio itself is only split later, by + * migrate_vma_pages() / migrate_device_pages(), so its order cannot be used to + * detect the demotion - the destination has to be inspected instead. + */ +static void drm_pagemap_migrate_populate_src_pages(struct page **pages, + unsigned long *src_mpfn, + unsigned long *dst_mpfn, + unsigned long npages) +{ + unsigned long i; + + for (i = 0; i < npages;) { + struct page *page = migrate_pfn_to_page(src_mpfn[i]); + unsigned int order = 0; + unsigned long j, nr; + + if (!page) { + i++; + continue; + } + + order = folio_order(page_folio(page)); + nr = NR_PAGES(order); + + if (order && !(dst_mpfn[i] & MIGRATE_PFN_COMPOUND)) { + for (j = 0; j < nr && i + j < npages; j++) + pages[i + j] = folio_page(page_folio(page), j); + } else { + pages[i] = page; + } + + i += nr; + } +} + /** * drm_pagemap_migrate_unmap_pages() - Unmap pages previously mapped for GPU SVM migration * @dev: The device for which the pages were mapped @@ -875,6 +927,7 @@ static int drm_pagemap_migrate_populate_ram_pfn(struct vm_area_struct *vas, struct page *page = NULL, *src_page; struct folio *folio; unsigned int order = 0; + gfp_t gfp = GFP_HIGHUSER; if (!(src_mpfn[i] & MIGRATE_PFN_MIGRATE)) goto next; @@ -891,11 +944,51 @@ static int drm_pagemap_migrate_populate_ram_pfn(struct vm_area_struct *vas, order = folio_order(page_folio(src_page)); - /* TODO: Support fallback to single pages if THP allocation fails */ + /* + * A large source folio is always collected whole, at its head + * page, PMD aligned and flagged MIGRATE_PFN_COMPOUND: anything + * else is split before it reaches us, either by + * migrate_vma_collect_pmd() or, for the eviction path, by + * migrate_device_pfns(). Both the order-0 fallback below and + * drm_pagemap_migrate_populate_src_pages() rely on that, as + * they index the folio from @i. + */ + WARN_ON_ONCE(order && + (src_page != folio_page(page_folio(src_page), 0) || + !(src_mpfn[i] & MIGRATE_PFN_COMPOUND))); + + if (order) + gfp |= __GFP_NOWARN; + if (vas) - folio = vma_alloc_folio(GFP_HIGHUSER, order, vas, addr); + folio = vma_alloc_folio(gfp, order, vas, addr); else - folio = folio_alloc(GFP_HIGHUSER, order); + folio = folio_alloc(gfp, order); + + if (!folio && order) { + /* + * Higher-order allocation failed, fall back to + * order-0 allocations for the entire range covered + * by the original higher-order allocation, without + * setting MIGRATE_PFN_COMPOUND, until we move past + * that range. + */ + unsigned long nr = NR_PAGES(order); + unsigned long j; + + gfp &= ~__GFP_NOWARN; + for (j = 0; j < nr && i < npages; j++, i++, addr += PAGE_SIZE) { + folio = vas ? + vma_alloc_folio(gfp, 0, vas, addr) : + folio_alloc(gfp, 0); + if (!folio) + goto free_pages; + + page = folio_page(folio, 0); + mpfn[i] = migrate_pfn(page_to_pfn(page)); + } + continue; + } if (!folio) goto free_pages; @@ -940,11 +1033,11 @@ static int drm_pagemap_migrate_populate_ram_pfn(struct vm_area_struct *vas, if (!page) goto next_put; + order = folio_order(page_folio(page)); + put_page(page); mpfn[i] = 0; - order = folio_order(page_folio(page)); - next_put: i += NR_PAGES(order); } @@ -1225,7 +1318,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) unsigned long *src, *dst; struct drm_pagemap_addr *pagemap_addr; void *buf; - int i, err = 0; + int err = 0; unsigned int retry_count = 2; npages = devmem_allocation->size >> PAGE_SHIFT; @@ -1268,15 +1361,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) if (err) goto err_finalize; - for (i = 0; i < npages;) { - unsigned int order = 0; - - pages[i] = migrate_pfn_to_page(src[i]); - if (pages[i]) - order = folio_order(page_folio(pages[i])); - - i += NR_PAGES(order); - } + drm_pagemap_migrate_populate_src_pages(pages, src, dst, npages); err = ops->copy_to_ram(pages, pagemap_addr, npages, NULL); if (err) @@ -1344,7 +1429,7 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, struct drm_pagemap_addr *pagemap_addr; unsigned long start, end; void *buf; - int i, err = 0; + int err = 0; zdd = drm_pagemap_page_zone_device_data(page); if (time_before64(get_jiffies_64(), zdd->devmem_allocation->timeslice_expiration)) @@ -1401,15 +1486,8 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, if (err) goto err_finalize; - for (i = 0; i < npages;) { - unsigned int order = 0; - - pages[i] = migrate_pfn_to_page(migrate.src[i]); - if (pages[i]) - order = folio_order(page_folio(pages[i])); - - i += NR_PAGES(order); - } + drm_pagemap_migrate_populate_src_pages(pages, migrate.src, migrate.dst, + npages); err = ops->copy_to_ram(pages, pagemap_addr, npages, NULL); if (err) From 88f8113ab118ed0e187331324d6b60c694b2e0e2 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 7 Aug 2026 13:56:21 +0200 Subject: [PATCH 52/53] drm/amd/display: use plane color_mgmt_changed to track colorop changes This is a resubmission of commit d79716401a95 ("drm/amd/display: use plane color_mgmt_changed to track colorop changes") whose change was reverted by commit 0461ba9a7994 ("Merge tag 'amd-drm-next-7.3-2026-07-02' of https://gitlab.freedesktop.org/agd5f/linux into drm-next") during a merge conflict resolution. Original commit message: ``` Ensure the driver tracks changes in any colorop property of a plane color pipeline by using the same mechanism of CRTC color management and update plane color blocks when any colorop property changes. It fixes an issue observed on gamescope settings for night mode which is done via shaper/3D-LUT updates. ``` Fixes: 0461ba9a7994 ("Merge tag 'amd-drm-next-7.3-2026-07-02' of https://gitlab.freedesktop.org/agd5f/linux into drm-next") Acked-by: Alex Deucher Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260807115712.22423-1-mwen@igalia.com --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 ec483276d753..2fe934036e36 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3879,7 +3879,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, continue; bundle->surface_updates[planes_count].surface = dc_plane; - if (new_pcrtc_state->color_mgmt_changed) { + if (new_pcrtc_state->color_mgmt_changed || new_plane_state->color_mgmt_changed) { bundle->surface_updates[planes_count].gamma = &dc_plane->gamma_correction; bundle->surface_updates[planes_count].in_transfer_func = &dc_plane->in_transfer_func; bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix; @@ -5698,6 +5698,10 @@ static bool should_reset_plane(struct drm_atomic_commit *state, if (new_crtc_state->color_mgmt_changed) return true; + /* Plane color pipeline or its colorop changes. */ + if (new_plane_state->color_mgmt_changed) + return true; + /* * On zpos change, planes need to be reordered by removing and re-adding * them one by one to the dc state, in order of descending zpos. From d3609b540838945ab2ca5b65f32a2eb67bb284c8 Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Thu, 23 Jul 2026 10:01:36 +0000 Subject: [PATCH 53/53] MAINTAINERS, mailmap: use Aditya Garg's linux.dev account Due to non standard IMAP and SMTP protocols by Proton Mail, the account was giving trouble. Since my linux.dev account has been approved, all communication related to Linux development shall now be done there. Signed-off-by: Aditya Garg Acked-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260723100136.14467-1-aditya.garg@linux.dev --- .mailmap | 3 ++- MAINTAINERS | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 6803f3bd2865..08c84f5c90d2 100644 --- a/.mailmap +++ b/.mailmap @@ -19,7 +19,8 @@ Abhinav Kumar Ahmad Masri Adam Oldham Adam Radford -Aditya Garg +Aditya Garg +Aditya Garg Adriana Reus Adrian Bunk Ajay Kaher diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..c7e62407de32 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8039,7 +8039,7 @@ F: drivers/gpu/drm/sun4i/sun8i* DRM DRIVER FOR APPLE TOUCH BARS M: Aun-Ali Zaidi -M: Aditya Garg +M: Aditya Garg L: dri-devel@lists.freedesktop.org S: Maintained T: git https://gitlab.freedesktop.org/drm/misc/kernel.git