From 9cff90774872ed6613b7571ce018b5b455d86890 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Wed, 13 May 2026 13:54:34 -0500 Subject: [PATCH 01/38] accel: ethosu: Validate SRAM size on submit Regions with a BO are checked against the BO size, but the SRAM region is not. The SRAM region doesn't have a BO, but the command stream region size should be checked against the SRAM size. The job's "sram_size" isn't useful here because an evil userspace could lie about the size. Signed-off-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20260513185434.1667045-1-robh@kernel.org Signed-off-by: Tomeu Vizoso Reviewed-by: Tomeu Vizoso --- drivers/accel/ethosu/ethosu_job.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index 418463c03bfb..b76924645aaa 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -416,9 +416,21 @@ static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file struct drm_gem_object *gem; /* Can only omit a BO handle if the region is not used or used for SRAM */ - if (!job->region_bo_handles[i] && - (!cmd_info->region_size[i] || (i == ETHOSU_SRAM_REGION && job->sram_size))) - continue; + if (!job->region_bo_handles[i]) { + if (!cmd_info->region_size[i]) + continue; + if (i == ETHOSU_SRAM_REGION) { + if (cmd_info->region_size[i] <= edev->npu_info.sram_size) + continue; + + dev_err(dev->dev, + "cmd stream region %d size greater than SRAM size (%llu > %u)\n", + i, cmd_info->region_size[i], + edev->npu_info.sram_size); + ret = -EINVAL; + goto out_cleanup_job; + } + } if (job->region_bo_handles[i] && !cmd_info->region_size[i]) { dev_err(dev->dev, From e5b93bd6fdb92aa5e4689715d7e8487d9ce66a38 Mon Sep 17 00:00:00 2001 From: Liviu Dudau Date: Thu, 7 May 2026 11:50:46 +0100 Subject: [PATCH 02/38] drm/syncobj: Fix memory leak in drm_syncobj_find_fence() Commit 18226ba52159 ("drm/syncobj: reject invalid flags in drm_syncobj_find_fence") forgot to take into account the fact that drm_syncobj_find() takes a reference to syncobj and returns early without dropping the reference, leading to memory leaks. Fixes: 18226ba52159 ("drm/syncobj: reject invalid flags in drm_syncobj_find_fence") Reported by: Sam Spencer Signed-off-by: Liviu Dudau Acked-by: Erik Kurzinger Signed-off-by: Liviu Dudau Link: https://lore.kernel.org/all/20260507144425.2488057-1-liviu.dudau@arm.com --- drivers/gpu/drm/drm_syncobj.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 8d9fd1917c6e..c9dbf64c0c9f 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -442,13 +442,15 @@ int drm_syncobj_find_fence(struct drm_file *file_private, u64 timeout = nsecs_to_jiffies64(DRM_SYNCOBJ_WAIT_FOR_SUBMIT_TIMEOUT); int ret; - if (flags & ~DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT) - return -EINVAL; - if (!syncobj) return -ENOENT; - /* Waiting for userspace with locks help is illegal cause that can + if (flags & ~DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT) { + ret = -EINVAL; + goto out; + } + + /* Waiting for userspace with locks held is illegal cause that can * trivial deadlock with page faults for example. Make lockdep complain * about it early on. */ From 02f48ffdf96c83ca3e6600fe5dec872b34b68775 Mon Sep 17 00:00:00 2001 From: Harrison Vanderbyl Date: Fri, 15 May 2026 15:41:50 +1000 Subject: [PATCH 03/38] drm/panel-edp: Add panel for Surface Pro 12in Add an entry for the BOE NE120DRM-N28 panel, used in the Microsoft Surface Pro 12-inch. The values chosen were tested to be working fine for wake from sleep and hibernation. Panel edid: 00 ff ff ff ff ff ff 00 09 e5 c9 0c a0 06 00 07 0a 22 01 04 a5 19 11 78 07 9f 15 a6 55 4c 9b 25 0e 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 62 53 94 a0 80 b8 2e 50 18 10 3a 00 fe a9 00 00 00 1a 13 7d 94 a0 80 b8 2e 50 18 10 3a 00 fe a9 00 00 00 1a 00 00 00 fd 00 18 5a 5b 88 20 01 0a 20 20 20 20 20 20 00 00 00 fc 00 4e 45 31 32 30 44 52 4d 2d 4e 32 38 0a 00 0a Signed-off-by: Harrison Vanderbyl Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/9e749a3a483e4a3c684eac3ee6a4b241c94a0362.1778822464.git.harrison.vanderbyl@gmail.com --- drivers/gpu/drm/panel/panel-edp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 241915e9f495..cb7d6c43466e 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -2030,6 +2030,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('B', 'O', 'E', 0x0c6f, &delay_200_500_e50, "NV140FHM-N40"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0c93, &delay_200_500_e200, "Unknown"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cb6, &delay_200_500_e200, "NT116WHM-N44"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cc9, &delay_200_500_e50, "NE120DRM-N28"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cf2, &delay_200_500_e200, "NV156FHM-N4S"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cf6, &delay_200_500_e200_d100, "NV140WUM-N64"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cfa, &delay_200_500_e50, "NV116WHM-A4D"), From 1a8f537f5a1eeac941f262fe73078d6b08ba83c0 Mon Sep 17 00:00:00 2001 From: Ashutosh Desai Date: Sun, 10 May 2026 20:17:33 +0000 Subject: [PATCH 04/38] drm/dp/mst: fix OOB reads in remote DPCD/I2C sideband reply parsers drm_dp_sideband_parse_remote_dpcd_read() reads num_bytes from the raw message and then unconditionally does: memcpy(bytes, &raw->msg[idx], num_bytes); without checking that idx + num_bytes <= raw->curlen. raw->msg[] is 256 bytes; if a malicious or misbehaving MST hub sets num_bytes larger than the remaining payload, the memcpy reads past the received data into whatever follows in raw->msg[]. drm_dp_sideband_parse_remote_i2c_read_ack() has the same flaw (noted with a /* TODO check */ comment since the code was introduced). Fix both functions by using a single combined check (idx + num_bytes > curlen) before each memcpy. Since num_bytes is u8, it is always >= 0, so this strictly subsumes the simpler idx > curlen form and no separate step is needed. Fixes: ad7f8a1f9ced ("drm/helper: add Displayport multi-stream helper (v0.6)") Cc: # v3.17+ Signed-off-by: Ashutosh Desai Reviewed-by: Lyude Paul [added missing fixes tag] Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260510201733.2882224-1-ashutoshdesai993@gmail.com --- drivers/gpu/drm/display/drm_dp_mst_topology.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c index fe72245fb9ca..2ca1f2e9712c 100644 --- a/drivers/gpu/drm/display/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c @@ -880,7 +880,7 @@ static bool drm_dp_sideband_parse_remote_dpcd_read(struct drm_dp_sideband_msg_rx goto fail_len; repmsg->u.remote_dpcd_read_ack.num_bytes = raw->msg[idx]; idx++; - if (idx > raw->curlen) + if (idx + repmsg->u.remote_dpcd_read_ack.num_bytes > raw->curlen) goto fail_len; memcpy(repmsg->u.remote_dpcd_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_dpcd_read_ack.num_bytes); @@ -916,7 +916,9 @@ static bool drm_dp_sideband_parse_remote_i2c_read_ack(struct drm_dp_sideband_msg goto fail_len; repmsg->u.remote_i2c_read_ack.num_bytes = raw->msg[idx]; idx++; - /* TODO check */ + if (idx + repmsg->u.remote_i2c_read_ack.num_bytes > raw->curlen) + goto fail_len; + memcpy(repmsg->u.remote_i2c_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_i2c_read_ack.num_bytes); return true; fail_len: From 6b89ba3dba2f583626fb693e47e951ffb8bf591f Mon Sep 17 00:00:00 2001 From: Ashutosh Desai Date: Sun, 10 May 2026 20:31:28 +0000 Subject: [PATCH 05/38] drm/dp/mst: fix OOB reads on 2-byte fields in sideband reply parsers Three sideband reply parsers read 16-bit fields as: val = (raw->msg[idx] << 8) | (raw->msg[idx+1]); and check bounds only after the fact. When idx == raw->curlen, raw->msg[idx+1] reads one byte past the received message data into the following struct fields (curchunk_len, curchunk_idx, curlen). Affected functions: - drm_dp_sideband_parse_enum_path_resources_ack() full_payload_bw_number and avail_payload_bw_number fields - drm_dp_sideband_parse_allocate_payload_ack() allocated_pbn field - drm_dp_sideband_parse_query_payload_ack() allocated_pbn field Fix by using a single combined check (idx + 2 > curlen) before each 2-byte read. Since the check is strictly tighter than idx > curlen, no separate step is needed. Fixes: ad7f8a1f9ced ("drm/helper: add Displayport multi-stream helper (v0.6)") Cc: # v3.17+ Signed-off-by: Ashutosh Desai Reviewed-by: Lyude Paul [added fixes tag] Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260510203128.2884846-1-ashutoshdesai993@gmail.com --- drivers/gpu/drm/display/drm_dp_mst_topology.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c index 2ca1f2e9712c..4de36fda0544 100644 --- a/drivers/gpu/drm/display/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c @@ -934,16 +934,13 @@ static bool drm_dp_sideband_parse_enum_path_resources_ack(struct drm_dp_sideband repmsg->u.path_resources.port_number = (raw->msg[idx] >> 4) & 0xf; repmsg->u.path_resources.fec_capable = raw->msg[idx] & 0x1; idx++; - if (idx > raw->curlen) + if (idx + 2 > raw->curlen) goto fail_len; repmsg->u.path_resources.full_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); idx += 2; - if (idx > raw->curlen) + if (idx + 2 > raw->curlen) goto fail_len; repmsg->u.path_resources.avail_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; return true; fail_len: DRM_DEBUG_KMS("enum resource parse length fail %d %d\n", idx, raw->curlen); @@ -961,12 +958,9 @@ static bool drm_dp_sideband_parse_allocate_payload_ack(struct drm_dp_sideband_ms goto fail_len; repmsg->u.allocate_payload.vcpi = raw->msg[idx]; idx++; - if (idx > raw->curlen) + if (idx + 2 > raw->curlen) goto fail_len; repmsg->u.allocate_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx+1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; return true; fail_len: DRM_DEBUG_KMS("allocate payload parse length fail %d %d\n", idx, raw->curlen); @@ -980,12 +974,9 @@ static bool drm_dp_sideband_parse_query_payload_ack(struct drm_dp_sideband_msg_r repmsg->u.query_payload.port_number = (raw->msg[idx] >> 4) & 0xf; idx++; - if (idx > raw->curlen) + if (idx + 2 > raw->curlen) goto fail_len; repmsg->u.query_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; return true; fail_len: DRM_DEBUG_KMS("query payload parse length fail %d %d\n", idx, raw->curlen); From dbc8fd7a03cbc0704e8e558a448015f620547a02 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Fri, 15 May 2026 09:19:22 -0700 Subject: [PATCH 06/38] accel/amdxdna: Add expandable device heap support Introduce an expandable device heap to avoid allocating a large heap upfront. Start with a smaller initial heap and grow it on demand. Return -EAGAIN when BO allocation fails due to insufficient heap space, allowing userspace to trigger heap expansion via a heap BO creation IOCTL and retry the allocation. Manage heap chunks using an xarray. On expansion, register new chunks with the firmware via MSG_OP_ADD_HOST_BUFFER. Since heap shrinking is not supported by the firmware, release all heap chunks on device close. Co-developed-by: Wendy Liang Signed-off-by: Wendy Liang Reviewed-by: Mario Limonciello Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260515161922.744647-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/aie2_ctx.c | 45 +++- drivers/accel/amdxdna/aie2_message.c | 52 +++- drivers/accel/amdxdna/aie2_msg_priv.h | 1 + drivers/accel/amdxdna/aie2_pci.c | 1 + drivers/accel/amdxdna/aie2_pci.h | 8 +- drivers/accel/amdxdna/amdxdna_ctx.c | 82 +++++- drivers/accel/amdxdna/amdxdna_ctx.h | 2 + drivers/accel/amdxdna/amdxdna_gem.c | 330 ++++++++++++++++++------ drivers/accel/amdxdna/amdxdna_gem.h | 16 +- drivers/accel/amdxdna/amdxdna_pci_drv.c | 12 +- drivers/accel/amdxdna/amdxdna_pci_drv.h | 8 +- drivers/accel/amdxdna/npu1_regs.c | 1 + drivers/accel/amdxdna/npu4_regs.c | 2 + drivers/accel/amdxdna/npu5_regs.c | 1 + drivers/accel/amdxdna/npu6_regs.c | 1 + 15 files changed, 463 insertions(+), 99 deletions(-) diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index 7d6094aefb6f..658a5fb1fda6 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -91,6 +91,7 @@ static void aie2_hwctx_stop(struct amdxdna_dev *xdna, struct amdxdna_hwctx *hwct static int aie2_hwctx_restart(struct amdxdna_dev *xdna, struct amdxdna_hwctx *hwctx) { struct amdxdna_gem_obj *heap = hwctx->priv->heap; + unsigned long heap_id; int ret; ret = aie2_create_context(xdna->dev_handle, hwctx); @@ -107,6 +108,17 @@ static int aie2_hwctx_restart(struct amdxdna_dev *xdna, struct amdxdna_hwctx *hw goto out; } + xa_for_each_range(&hwctx->client->dev_heap_xa, heap_id, heap, 1, + hwctx->last_attached_heap) { + ret = aie2_add_host_buf(xdna->dev_handle, hwctx->fw_ctx_id, + amdxdna_obj_dma_addr(heap), + heap->mem.size); + if (ret) { + XDNA_ERR(xdna, "Add heap %ld failed ret %d", heap_id, ret); + goto out; + } + } + ret = aie2_config_cu(hwctx, NULL); if (ret) { XDNA_ERR(xdna, "Config cu failed, ret %d", ret); @@ -650,7 +662,7 @@ int aie2_hwctx_init(struct amdxdna_hwctx *hwctx) hwctx->priv = priv; mutex_lock(&client->mm_lock); - heap = client->dev_heap; + heap = xa_load(&client->dev_heap_xa, 0); if (!heap) { XDNA_ERR(xdna, "The client dev heap object not exist"); mutex_unlock(&client->mm_lock); @@ -732,6 +744,12 @@ int aie2_hwctx_init(struct amdxdna_hwctx *hwctx) goto release_resource; } + ret = amdxdna_update_heap(client, hwctx); + if (ret) { + XDNA_ERR(xdna, "Update heap failed, ret %d", ret); + goto release_resource; + } + ret = aie2_ctx_syncobj_create(hwctx); if (ret) { XDNA_ERR(xdna, "Create syncobj failed, ret %d", ret); @@ -1161,3 +1179,28 @@ void aie2_hmm_invalidate(struct amdxdna_gem_obj *abo, else if (ret == -ERESTARTSYS) XDNA_DBG(xdna, "Wait for bo interrupted by signal"); } + +int aie2_hwctx_heap_expand(struct amdxdna_hwctx *hwctx, + struct amdxdna_gem_obj *heap) +{ + struct amdxdna_client *client = hwctx->client; + struct amdxdna_dev *xdna = client->xdna; + u64 addr; + int ret; + + ret = amdxdna_pm_resume_get_locked(xdna); + if (ret) + return ret; + + addr = amdxdna_obj_dma_addr(heap); + ret = aie2_add_host_buf(xdna->dev_handle, hwctx->fw_ctx_id, + addr, heap->mem.size); + if (ret) { + XDNA_ERR(xdna, "Add heap failed hwctx %s 0x%lx ret %d", + hwctx->name, heap->mem.size, ret); + } + + amdxdna_pm_suspend_put(xdna); + + return ret; +} diff --git a/drivers/accel/amdxdna/aie2_message.c b/drivers/accel/amdxdna/aie2_message.c index 1504295a21e4..c4b364801cc0 100644 --- a/drivers/accel/amdxdna/aie2_message.c +++ b/drivers/accel/amdxdna/aie2_message.c @@ -301,25 +301,59 @@ int aie2_destroy_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwc return ret; } -int aie2_map_host_buf(struct amdxdna_dev_hdl *ndev, u32 context_id, u64 addr, u64 size) +static int aie2_send_host_buf_msgs(struct amdxdna_dev_hdl *ndev, u32 context_id, + u64 addr, u64 size, u32 initial_opcode) { DECLARE_AIE_MSG(map_host_buffer, MSG_OP_MAP_HOST_BUFFER); struct amdxdna_dev *xdna = ndev->aie.xdna; + size_t chunk_size; int ret; - req.context_id = context_id; - req.buf_addr = addr; - req.buf_size = size; - ret = aie_send_mgmt_msg_wait(&ndev->aie, &msg); - if (ret) - return ret; + chunk_size = xdna->dev_info->dev_mem_size; + if (!size || !IS_ALIGNED(size, chunk_size)) { + XDNA_ERR(xdna, "Invalid size 0x%llx for chunk 0x%lx", + size, chunk_size); + return -EINVAL; + } - XDNA_DBG(xdna, "fw ctx %d map host buf addr 0x%llx size 0x%llx", - context_id, addr, size); + msg.opcode = initial_opcode; + do { + req.context_id = context_id; + req.buf_addr = addr; + req.buf_size = chunk_size; + ret = aie_send_mgmt_msg_wait(&ndev->aie, &msg); + if (ret) { + XDNA_ERR(xdna, "fw ctx %d addr 0x%llx size 0x%lx", + context_id, addr, chunk_size); + return ret; + } + + XDNA_DBG(xdna, "fw ctx %d host buf op 0x%x addr 0x%llx size 0x%lx", + context_id, msg.opcode, addr, chunk_size); + + addr += chunk_size; + size -= chunk_size; + msg.opcode = MSG_OP_ADD_HOST_BUFFER; + } while (size); return 0; } +int aie2_map_host_buf(struct amdxdna_dev_hdl *ndev, u32 context_id, u64 addr, u64 size) +{ + return aie2_send_host_buf_msgs(ndev, context_id, addr, size, + MSG_OP_MAP_HOST_BUFFER); +} + +int aie2_add_host_buf(struct amdxdna_dev_hdl *ndev, u32 context_id, u64 addr, u64 size) +{ + if (!AIE_FEATURE_ON(&ndev->aie, AIE2_ADD_HOST_BUFFER)) + return -EOPNOTSUPP; + + return aie2_send_host_buf_msgs(ndev, context_id, addr, size, + MSG_OP_ADD_HOST_BUFFER); +} + static int amdxdna_hwctx_col_map(struct amdxdna_hwctx *hwctx, void *arg) { u32 *bitmap = arg; diff --git a/drivers/accel/amdxdna/aie2_msg_priv.h b/drivers/accel/amdxdna/aie2_msg_priv.h index a41c9797e265..fd65a4236d49 100644 --- a/drivers/accel/amdxdna/aie2_msg_priv.h +++ b/drivers/accel/amdxdna/aie2_msg_priv.h @@ -33,6 +33,7 @@ enum aie2_msg_opcode { MSG_OP_REGISTER_ASYNC_EVENT_MSG = 0x10C, MSG_OP_UPDATE_PROPERTY = 0x113, MSG_OP_GET_APP_HEALTH = 0x114, + MSG_OP_ADD_HOST_BUFFER = 0x115, MSG_OP_GET_DEV_REVISION = 0x117, MSG_OP_MAX_DRV_OPCODE, MSG_OP_GET_PROTOCOL_VERSION = 0x301, diff --git a/drivers/accel/amdxdna/aie2_pci.c b/drivers/accel/amdxdna/aie2_pci.c index 6c8a0f70b73d..c4d345d4c76b 100644 --- a/drivers/accel/amdxdna/aie2_pci.c +++ b/drivers/accel/amdxdna/aie2_pci.c @@ -1241,4 +1241,5 @@ const struct amdxdna_dev_ops aie2_ops = { .hmm_invalidate = aie2_hmm_invalidate, .get_array = aie2_get_array, .get_dev_revision = aie2_get_dev_rev, + .hwctx_heap_expand = aie2_hwctx_heap_expand, }; diff --git a/drivers/accel/amdxdna/aie2_pci.h b/drivers/accel/amdxdna/aie2_pci.h index 84bdb3f8b8f9..77648cc548b6 100644 --- a/drivers/accel/amdxdna/aie2_pci.h +++ b/drivers/accel/amdxdna/aie2_pci.h @@ -15,8 +15,9 @@ #include "amdxdna_mailbox.h" /* Firmware determines device memory base address and size */ -#define AIE2_DEVM_BASE 0x4000000 -#define AIE2_DEVM_SIZE SZ_64M +#define AIE2_DEVM_BASE 0x4000000 +#define AIE2_DEVM_SIZE SZ_64M +#define AIE2_DEVM_MAX_SIZE SZ_512M #define NDEV2PDEV(ndev) (to_pci_dev((ndev)->aie.xdna->ddev.dev)) @@ -198,6 +199,7 @@ enum aie2_fw_feature { AIE2_PREEMPT, AIE2_TEMPORAL_ONLY, AIE2_APP_HEALTH, + AIE2_ADD_HOST_BUFFER, AIE2_UPDATE_PROPERTY, AIE2_GET_DEV_REVISION, AIE2_FEATURE_MAX @@ -271,6 +273,7 @@ int aie2_get_dev_revision(struct amdxdna_dev_hdl *ndev, enum aie2_dev_revision * int aie2_create_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwctx); int aie2_destroy_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwctx); int aie2_map_host_buf(struct amdxdna_dev_hdl *ndev, u32 context_id, u64 addr, u64 size); +int aie2_add_host_buf(struct amdxdna_dev_hdl *ndev, u32 context_id, u64 addr, u64 size); int aie2_query_status(struct amdxdna_dev_hdl *ndev, char __user *buf, u32 size, u32 *cols_filled); int aie2_query_telemetry(struct amdxdna_dev_hdl *ndev, char __user *buf, u32 size, @@ -302,5 +305,6 @@ void aie2_hwctx_suspend(struct amdxdna_client *client); int aie2_hwctx_resume(struct amdxdna_client *client); int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job, u64 *seq); void aie2_hmm_invalidate(struct amdxdna_gem_obj *abo, unsigned long cur_seq); +int aie2_hwctx_heap_expand(struct amdxdna_hwctx *hwctx, struct amdxdna_gem_obj *heap); #endif /* _AIE2_PCI_H_ */ diff --git a/drivers/accel/amdxdna/amdxdna_ctx.c b/drivers/accel/amdxdna/amdxdna_ctx.c index b79229a63af3..855da8c79a1c 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.c +++ b/drivers/accel/amdxdna/amdxdna_ctx.c @@ -61,16 +61,35 @@ static struct dma_fence *amdxdna_fence_create(struct amdxdna_hwctx *hwctx) return &fence->base; } +static void amdxdna_hwctx_release_expanded_heap(struct amdxdna_hwctx *hwctx) +{ + struct amdxdna_client *client = hwctx->client; + struct amdxdna_gem_obj *heap; + unsigned long heap_id; + + mutex_lock(&client->mm_lock); + if (hwctx->last_attached_heap) { + xa_for_each_range(&client->dev_heap_xa, heap_id, heap, 1, + hwctx->last_attached_heap) { + amdxdna_gem_unpin(heap); + drm_gem_object_put(to_gobj(heap)); + } + } + mutex_unlock(&client->mm_lock); +} + static void amdxdna_hwctx_destroy_rcu(struct amdxdna_hwctx *hwctx, struct srcu_struct *ss) { - struct amdxdna_dev *xdna = hwctx->client->xdna; + struct amdxdna_client *client = hwctx->client; + struct amdxdna_dev *xdna = client->xdna; synchronize_srcu(ss); /* At this point, user is not able to submit new commands */ xdna->dev_info->ops->hwctx_fini(hwctx); + amdxdna_hwctx_release_expanded_heap(hwctx); kfree(hwctx->name); kfree(hwctx); } @@ -238,7 +257,7 @@ int amdxdna_drm_create_hwctx_ioctl(struct drm_device *dev, void *data, struct dr ret = xdna->dev_info->ops->hwctx_init(hwctx); if (ret) { XDNA_ERR(xdna, "Init hwctx failed, ret %d", ret); - goto dev_exit; + goto release_expanded_heap; } hwctx->name = kasprintf(GFP_KERNEL, "hwctx.%d.%d", client->pid, hwctx->fw_ctx_id); @@ -269,7 +288,8 @@ int amdxdna_drm_create_hwctx_ioctl(struct drm_device *dev, void *data, struct dr kfree(hwctx->name); fini_hwctx: xdna->dev_info->ops->hwctx_fini(hwctx); -dev_exit: +release_expanded_heap: + amdxdna_hwctx_release_expanded_heap(hwctx); drm_dev_exit(idx); free_hwctx: kfree(hwctx); @@ -407,6 +427,62 @@ int amdxdna_hwctx_sync_debug_bo(struct amdxdna_client *client, u32 debug_bo_hdl) return ret; } +static int amdxdna_hwctx_expand_heap(struct amdxdna_hwctx *hwctx) +{ + struct amdxdna_client *client = hwctx->client; + struct amdxdna_dev *xdna = client->xdna; + struct amdxdna_gem_obj *heap; + unsigned long heap_id, nid; + int ret = 0; + + nid = hwctx->last_attached_heap + 1; + if (nid == client->dev_heap_nid) + goto out; + + xa_for_each_range(&client->dev_heap_xa, heap_id, heap, + nid, client->dev_heap_nid) { + drm_gem_object_get(to_gobj(heap)); + ret = amdxdna_gem_pin(heap); + if (ret) { + drm_gem_object_put(to_gobj(heap)); + break; + } + + mutex_unlock(&client->mm_lock); + ret = xdna->dev_info->ops->hwctx_heap_expand(hwctx, heap); + mutex_lock(&client->mm_lock); + if (ret) { + amdxdna_gem_unpin(heap); + drm_gem_object_put(to_gobj(heap)); + break; + } + + hwctx->last_attached_heap = heap_id; + } + +out: + return ret; +} + +int amdxdna_update_heap(struct amdxdna_client *client, struct amdxdna_hwctx *hwctx) +{ + unsigned long hwctx_id; + int ret; + + guard(mutex)(&client->mm_lock); + + if (hwctx) + return amdxdna_hwctx_expand_heap(hwctx); + + amdxdna_for_each_hwctx(client, hwctx_id, hwctx) { + ret = amdxdna_hwctx_expand_heap(hwctx); + if (ret) + return ret; + } + + return 0; +} + static void amdxdna_arg_bos_put(struct amdxdna_sched_job *job) { diff --git a/drivers/accel/amdxdna/amdxdna_ctx.h b/drivers/accel/amdxdna/amdxdna_ctx.h index 6e3c6371a088..aaae16430466 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.h +++ b/drivers/accel/amdxdna/amdxdna_ctx.h @@ -109,6 +109,7 @@ struct amdxdna_hwctx { u32 umq_bo_hdl; u32 doorbell_offset; u32 num_unused_col; + u32 last_attached_heap; struct amdxdna_qos_info qos; struct amdxdna_hwctx_param_config_cu *cus; @@ -205,6 +206,7 @@ void amdxdna_hwctx_remove_all(struct amdxdna_client *client); int amdxdna_hwctx_walk(struct amdxdna_client *client, void *arg, int (*walk)(struct amdxdna_hwctx *hwctx, void *arg)); int amdxdna_hwctx_sync_debug_bo(struct amdxdna_client *client, u32 debug_bo_hdl); +int amdxdna_update_heap(struct amdxdna_client *client, struct amdxdna_hwctx *hwctx); int amdxdna_cmd_submit(struct amdxdna_client *client, struct amdxdna_drv_cmd *drv_cmd, u32 cmd_bo_hdls, diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 319d2064fafa..2dfdc56ba91d 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -24,6 +24,77 @@ MODULE_IMPORT_NS("DMA_BUF"); +/* + * The dev BO could be across multiple heap BO chunks. The heap chunks should + * be mapped to userspace and the userspace virtual address should be + * contiguous. + */ +static int +amdxdna_init_dev_bo(struct amdxdna_gem_obj *dev_bo) +{ + struct amdxdna_client *client = dev_bo->client; + struct amdxdna_dev *xdna = client->xdna; + struct amdxdna_gem_obj *heap; + u64 heap_addr, exp_heap_uva; + u32 heap_id; + + if (xa_empty(&client->dev_heap_xa)) { + XDNA_DBG(xdna, "Empty heap xa"); + return -EAGAIN; + } + + for (heap_id = 0; heap_id < client->dev_heap_nid; heap_id++) { + heap = xa_load(&client->dev_heap_xa, heap_id); + if (!heap) { + XDNA_ERR(xdna, "Failed to load heap %d", heap_id); + return -EINVAL; + } + heap_addr = amdxdna_gem_dev_addr(heap); + if (heap_addr > dev_bo->mm_node.start) + break; + } + + heap_id--; + heap = xa_load(&client->dev_heap_xa, heap_id); + exp_heap_uva = amdxdna_gem_uva(heap); + heap_addr = amdxdna_gem_dev_addr(heap); + dev_bo->heap_start_id = heap_id; + dev_bo->mem.uva = dev_bo->mm_node.start - heap_addr + exp_heap_uva; + + for (; heap_id < client->dev_heap_nid; heap_id++) { + heap = xa_load(&client->dev_heap_xa, heap_id); + if (!heap) { + XDNA_ERR(xdna, "Failed to load heap %d", heap_id); + return -EINVAL; + } + heap_addr = amdxdna_gem_uva(heap); + if (heap_addr == AMDXDNA_INVALID_ADDR) { + XDNA_ERR(xdna, "Heap %d is not mapped", heap_id); + return -EAGAIN; + } + + if (heap_addr != exp_heap_uva) { + XDNA_ERR(xdna, "Heap %d uva is not contiguous", heap_id); + return -EINVAL; + } + + if (heap->dev_addr + heap->mem.size >= + dev_bo->mm_node.start + dev_bo->mem.size) + break; + + exp_heap_uva += heap->mem.size; + } + + if (heap_id == client->dev_heap_nid) { + XDNA_DBG(xdna, "Can not find heap end"); + return -EAGAIN; + } + + dev_bo->heap_end_id = heap_id; + + return 0; +} + static int amdxdna_gem_heap_alloc(struct amdxdna_gem_obj *abo) { @@ -31,32 +102,22 @@ amdxdna_gem_heap_alloc(struct amdxdna_gem_obj *abo) struct amdxdna_dev *xdna = client->xdna; struct amdxdna_mem *mem = &abo->mem; struct amdxdna_gem_obj *heap; + unsigned long heap_id; u32 align; int ret; mutex_lock(&client->mm_lock); - heap = client->dev_heap; - if (!heap) { - ret = -EINVAL; - goto unlock_out; - } - - if (amdxdna_gem_uva(heap) == AMDXDNA_INVALID_ADDR) { - XDNA_ERR(xdna, "Invalid dev heap userptr"); - ret = -EINVAL; - goto unlock_out; - } - - if (mem->size == 0 || mem->size > heap->mem.size) { - XDNA_ERR(xdna, "Invalid dev bo size 0x%lx, limit 0x%lx", - mem->size, heap->mem.size); + if (!mem->size || mem->size > xdna->dev_info->dev_heap_max_size) { + XDNA_ERR(xdna, "Invalid dev bo size 0x%lx, max heap 0x%lx", + mem->size, xdna->dev_info->dev_heap_max_size); ret = -EINVAL; goto unlock_out; } align = 1 << max(PAGE_SHIFT, xdna->dev_info->dev_mem_buf_shift); - ret = drm_mm_insert_node_generic(&heap->mm, &abo->mm_node, + ret = drm_mm_insert_node_generic(&client->dev_heap_mm, + &abo->mm_node, mem->size, align, 0, DRM_MM_INSERT_BEST); if (ret) { @@ -64,9 +125,16 @@ amdxdna_gem_heap_alloc(struct amdxdna_gem_obj *abo) goto unlock_out; } - client->heap_usage += mem->size; + ret = amdxdna_init_dev_bo(abo); + if (ret) { + drm_mm_remove_node(&abo->mm_node); + goto unlock_out; + } - drm_gem_object_get(to_gobj(heap)); + client->heap_usage += mem->size; + xa_for_each_range(&client->dev_heap_xa, heap_id, heap, + abo->heap_start_id, abo->heap_end_id) + drm_gem_object_get(to_gobj(heap)); unlock_out: mutex_unlock(&client->mm_lock); @@ -79,13 +147,16 @@ amdxdna_gem_heap_free(struct amdxdna_gem_obj *abo) { struct amdxdna_client *client = abo->client; struct amdxdna_gem_obj *heap; + unsigned long heap_id; mutex_lock(&client->mm_lock); drm_mm_remove_node(&abo->mm_node); client->heap_usage -= abo->mem.size; - heap = client->dev_heap; - drm_gem_object_put(to_gobj(heap)); + + xa_for_each_range(&client->dev_heap_xa, heap_id, heap, + abo->heap_start_id, abo->heap_end_id) + drm_gem_object_put(to_gobj(heap)); mutex_unlock(&client->mm_lock); } @@ -161,31 +232,13 @@ static void amdxdna_gem_vunmap(struct amdxdna_gem_obj *abo) } } -/* - * Obtain the user virtual address for accessing the BO. - * It can be used for device to access the BO when PASID is enabled. - */ -u64 amdxdna_gem_uva(struct amdxdna_gem_obj *abo) -{ - if (abo->type == AMDXDNA_BO_DEV) { - struct amdxdna_gem_obj *heap = abo->client->dev_heap; - u64 off = amdxdna_dev_bo_offset(abo); - - if (amdxdna_gem_uva(heap) != AMDXDNA_INVALID_ADDR) - return amdxdna_gem_uva(heap) + off; - return AMDXDNA_INVALID_ADDR; - } - - return abo->mem.uva; -} - /* * Obtain the address for device to access the BO. */ u64 amdxdna_gem_dev_addr(struct amdxdna_gem_obj *abo) { if (abo->type == AMDXDNA_BO_DEV_HEAP) - return abo->client->xdna->dev_info->dev_mem_base; + return abo->dev_addr; if (abo->type == AMDXDNA_BO_DEV) return abo->mm_node.start; return amdxdna_obj_dma_addr(abo); @@ -566,9 +619,6 @@ static void amdxdna_gem_obj_free(struct drm_gem_object *gobj) if (abo->pinned) amdxdna_gem_unpin(abo); - if (abo->type == AMDXDNA_BO_DEV_HEAP) - drm_mm_takedown(&abo->mm); - amdxdna_dma_unmap_bo(xdna, abo); amdxdna_gem_vunmap(abo); mutex_destroy(&abo->lock); @@ -654,11 +704,23 @@ static void amdxdna_gem_obj_vunmap(struct drm_gem_object *obj, struct iosys_map static int amdxdna_gem_dev_obj_vmap(struct drm_gem_object *obj, struct iosys_map *map) { struct amdxdna_gem_obj *abo = to_xdna_obj(obj); - void *base = amdxdna_gem_vmap(abo->client->dev_heap); - u64 offset = amdxdna_dev_bo_offset(abo); + struct amdxdna_gem_obj *heap; + void *base; + u64 offset; + /* vmap dev bo which is across more than 1 heap is not allowed */ + if (abo->heap_start_id != abo->heap_end_id) + return -ENOMEM; + + heap = xa_load(&abo->client->dev_heap_xa, abo->heap_start_id); + if (!heap) + return -ENOMEM; + + base = amdxdna_gem_vmap(heap); if (!base) return -ENOMEM; + + offset = amdxdna_gem_dev_addr(abo) - amdxdna_gem_dev_addr(heap); iosys_map_set_vaddr(map, base + offset); return 0; } @@ -873,15 +935,32 @@ amdxdna_drm_create_dev_heap_bo(struct drm_device *dev, /* Set up heap for this client. */ mutex_lock(&client->mm_lock); - if (client->dev_heap) { - XDNA_DBG(client->xdna, "dev heap is already created"); - ret = -EBUSY; + if (!xdna->dev_info->ops->hwctx_heap_expand && + client->dev_heap_nid > 0) { + XDNA_ERR(xdna, "Heap expansion is not supported"); + ret = -EOPNOTSUPP; goto mm_unlock; } - client->dev_heap = abo; - drm_gem_object_get(to_gobj(abo)); - drm_mm_init(&abo->mm, xdna->dev_info->dev_mem_base, abo->mem.size); + if (client->total_heap_size + abo->mem.size > + xdna->dev_info->dev_heap_max_size) { + XDNA_ERR(xdna, "Heap size 0x%lx + 0x%lx exceeds max 0x%lx", + client->total_heap_size, abo->mem.size, + xdna->dev_info->dev_heap_max_size); + ret = -ENOSPC; + goto mm_unlock; + } + + ret = xa_insert(&client->dev_heap_xa, client->dev_heap_nid, abo, GFP_KERNEL); + if (ret) { + XDNA_ERR(xdna, "Add heap failed %d", ret); + goto mm_unlock; + } + + abo->dev_addr = xdna->dev_info->dev_mem_base + client->total_heap_size; + client->total_heap_size += abo->mem.size; + client->dev_heap_nid++; + drm_gem_object_get(to_gobj(abo)); mutex_unlock(&client->mm_lock); @@ -924,10 +1003,10 @@ amdxdna_drm_create_dev_bo(struct drm_device *dev, ret = amdxdna_gem_heap_alloc(abo); if (ret) { - XDNA_ERR(xdna, "Failed to alloc dev bo memory, ret %d", ret); amdxdna_gem_destroy_obj(abo); return ERR_PTR(ret); } + drm_gem_private_object_init(dev, gobj, aligned_sz); return abo; @@ -935,6 +1014,7 @@ amdxdna_drm_create_dev_bo(struct drm_device *dev, int amdxdna_drm_create_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) { + struct amdxdna_client *client = filp->driver_priv; struct amdxdna_dev *xdna = to_xdna_dev(dev); struct amdxdna_drm_create_bo *args = data; struct amdxdna_gem_obj *abo; @@ -955,6 +1035,13 @@ int amdxdna_drm_create_bo_ioctl(struct drm_device *dev, void *data, struct drm_f break; case AMDXDNA_BO_DEV: abo = amdxdna_drm_create_dev_bo(dev, args, filp); + if (!IS_ERR(abo)) { + mutex_lock(&xdna->dev_lock); + ret = amdxdna_update_heap(client, NULL); + mutex_unlock(&xdna->dev_lock); + if (ret) + goto put_obj; + } break; default: return -EINVAL; @@ -978,14 +1065,11 @@ int amdxdna_drm_create_bo_ioctl(struct drm_device *dev, void *data, struct drm_f return ret; } -int amdxdna_gem_pin_nolock(struct amdxdna_gem_obj *abo) +static int amdxdna_bo_pin(struct amdxdna_gem_obj *abo) { struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev); int ret; - if (abo->type == AMDXDNA_BO_DEV) - abo = abo->client->dev_heap; - if (is_import_bo(abo)) return 0; @@ -995,6 +1079,45 @@ int amdxdna_gem_pin_nolock(struct amdxdna_gem_obj *abo) return ret; } +static void amdxdna_bo_unpin(struct amdxdna_gem_obj *abo) +{ + struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev); + + if (is_import_bo(abo)) + return; + + drm_gem_shmem_unpin(&abo->base); + + XDNA_DBG(xdna, "BO type %d", abo->type); +} + +int amdxdna_gem_pin_nolock(struct amdxdna_gem_obj *abo) +{ + struct amdxdna_client *client = abo->client; + struct amdxdna_gem_obj *heap; + unsigned long heap_id, last = ULONG_MAX; + int ret = 0; + + if (abo->type != AMDXDNA_BO_DEV) + return amdxdna_bo_pin(abo); + + xa_for_each_range(&client->dev_heap_xa, heap_id, heap, + abo->heap_start_id, abo->heap_end_id) { + ret = amdxdna_bo_pin(heap); + if (ret) + break; + last = heap_id; + } + + if (ret && last <= abo->heap_end_id) { + xa_for_each_range(&client->dev_heap_xa, heap_id, heap, + abo->heap_start_id, last) + amdxdna_bo_unpin(heap); + } + + return ret; +} + int amdxdna_gem_pin(struct amdxdna_gem_obj *abo) { int ret; @@ -1008,14 +1131,18 @@ int amdxdna_gem_pin(struct amdxdna_gem_obj *abo) void amdxdna_gem_unpin(struct amdxdna_gem_obj *abo) { - if (abo->type == AMDXDNA_BO_DEV) - abo = abo->client->dev_heap; - - if (is_import_bo(abo)) - return; - mutex_lock(&abo->lock); - drm_gem_shmem_unpin(&abo->base); + if (abo->type == AMDXDNA_BO_DEV) { + struct amdxdna_gem_obj *heap; + unsigned long heap_id; + + xa_for_each_range(&abo->client->dev_heap_xa, heap_id, heap, + abo->heap_start_id, abo->heap_end_id) + amdxdna_bo_unpin(heap); + } else { + amdxdna_bo_unpin(abo); + } + mutex_unlock(&abo->lock); } @@ -1072,6 +1199,29 @@ int amdxdna_drm_get_bo_info_ioctl(struct drm_device *dev, void *data, struct drm return ret; } +static int amdxdna_flush_bo(struct amdxdna_gem_obj *abo, u64 offset, u64 size) +{ + u64 end; + + if (offset >= abo->mem.size) + return -EINVAL; + + if (check_add_overflow(offset, size, &end)) + return -EINVAL; + + size = min(abo->mem.size, end) - offset; + if (is_import_bo(abo)) + drm_clflush_sg(abo->base.sgt); + else 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); + else + return -EINVAL; + + return 0; +} + /* * The sync bo ioctl is to make sure the CPU cache is in sync with memory. * This is required because NPU is not cache coherent device. CPU cache @@ -1082,11 +1232,12 @@ int amdxdna_drm_get_bo_info_ioctl(struct drm_device *dev, void *data, struct drm int amdxdna_drm_sync_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) { + struct amdxdna_client *client = filp->driver_priv; struct amdxdna_dev *xdna = to_xdna_dev(dev); struct amdxdna_drm_sync_bo *args = data; struct amdxdna_gem_obj *abo; struct drm_gem_object *gobj; - int ret; + int ret = 0; gobj = drm_gem_object_lookup(filp, args->handle); if (!gobj) { @@ -1095,23 +1246,46 @@ int amdxdna_drm_sync_bo_ioctl(struct drm_device *dev, } abo = to_xdna_obj(gobj); - ret = amdxdna_gem_pin(abo); - if (ret) { - XDNA_ERR(xdna, "Pin BO %d failed, ret %d", args->handle, ret); - goto put_obj; + if (abo->type == AMDXDNA_BO_DEV) { + struct amdxdna_gem_obj *heap; + unsigned long heap_id; + u64 bo_start = amdxdna_gem_dev_addr(abo); + u64 flush_start = bo_start + args->offset; + u64 flush_end = flush_start + args->size; + + xa_for_each_range(&client->dev_heap_xa, heap_id, heap, + abo->heap_start_id, abo->heap_end_id) { + u64 heap_start = amdxdna_gem_dev_addr(heap); + u64 heap_end = heap_start + heap->mem.size; + u64 start = max(flush_start, heap_start); + u64 end = min(flush_end, heap_end); + + if (start >= end) + continue; + + ret = amdxdna_flush_bo(heap, start - heap_start, end - start); + if (ret) { + XDNA_ERR(xdna, "Failed to flush heap %ld ret %d", + heap_id, ret); + goto put_obj; + } + } + } else { + ret = amdxdna_gem_pin(abo); + if (ret) { + XDNA_ERR(xdna, "Pin BO %d failed, ret %d", args->handle, ret); + goto put_obj; + } + + ret = amdxdna_flush_bo(abo, args->offset, args->size); + amdxdna_gem_unpin(abo); + + if (ret) { + drm_WARN(&xdna->ddev, 1, "Can not get flush memory"); + goto put_obj; + } } - if (is_import_bo(abo)) - drm_clflush_sg(abo->base.sgt); - else if (amdxdna_gem_vmap(abo)) - drm_clflush_virt_range(amdxdna_gem_vmap(abo) + args->offset, args->size); - else if (abo->base.pages) - drm_clflush_pages(abo->base.pages, gobj->size >> PAGE_SHIFT); - else - drm_WARN(&xdna->ddev, 1, "Can not get flush memory"); - - amdxdna_gem_unpin(abo); - XDNA_DBG(xdna, "Sync bo %d offset 0x%llx, size 0x%llx\n", args->handle, args->offset, args->size); diff --git a/drivers/accel/amdxdna/amdxdna_gem.h b/drivers/accel/amdxdna/amdxdna_gem.h index 4fc48a1189d2..6a6df51969e0 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.h +++ b/drivers/accel/amdxdna/amdxdna_gem.h @@ -46,8 +46,10 @@ struct amdxdna_gem_obj { int open_ref; /* Below members are initialized when needed */ - struct drm_mm mm; /* For AMDXDNA_BO_DEV_HEAP */ struct drm_mm_node mm_node; /* For AMDXDNA_BO_DEV */ + u32 heap_start_id; + u32 heap_end_id; + u64 dev_addr; /* For heap bo */ u32 assigned_hwctx; struct dma_buf *dma_buf; struct dma_buf_attachment *attach; @@ -71,13 +73,21 @@ static inline void amdxdna_gem_put_obj(struct amdxdna_gem_obj *abo) drm_gem_object_put(to_gobj(abo)); } +/* + * Obtain the user virtual address for accessing the BO. + * It can be used for device to access the BO when PASID is enabled. + */ +static inline u64 amdxdna_gem_uva(struct amdxdna_gem_obj *abo) +{ + return abo->mem.uva; +} + void *amdxdna_gem_vmap(struct amdxdna_gem_obj *abo); -u64 amdxdna_gem_uva(struct amdxdna_gem_obj *abo); u64 amdxdna_gem_dev_addr(struct amdxdna_gem_obj *abo); static inline u64 amdxdna_dev_bo_offset(struct amdxdna_gem_obj *abo) { - return amdxdna_gem_dev_addr(abo) - amdxdna_gem_dev_addr(abo->client->dev_heap); + return amdxdna_gem_dev_addr(abo) - abo->client->xdna->dev_info->dev_mem_base; } static inline u64 amdxdna_obj_dma_addr(struct amdxdna_gem_obj *abo) diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.c b/drivers/accel/amdxdna/amdxdna_pci_drv.c index a6e9be7960c2..c677293c1ae7 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.c +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.c @@ -126,6 +126,9 @@ static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp) mmgrab(client->mm); init_srcu_struct(&client->hwctx_srcu); xa_init_flags(&client->hwctx_xa, XA_FLAGS_ALLOC); + xa_init_flags(&client->dev_heap_xa, XA_FLAGS_ALLOC); + drm_mm_init(&client->dev_heap_mm, xdna->dev_info->dev_mem_base, + xdna->dev_info->dev_heap_max_size); mutex_init(&client->mm_lock); mutex_lock(&xdna->dev_lock); @@ -141,13 +144,18 @@ static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp) static void amdxdna_client_cleanup(struct amdxdna_client *client) { + struct amdxdna_gem_obj *heap; + unsigned long heap_id; + list_del(&client->node); amdxdna_hwctx_remove_all(client); xa_destroy(&client->hwctx_xa); cleanup_srcu_struct(&client->hwctx_srcu); - if (client->dev_heap) - drm_gem_object_put(to_gobj(client->dev_heap)); + xa_for_each(&client->dev_heap_xa, heap_id, heap) + drm_gem_object_put(to_gobj(heap)); + xa_destroy(&client->dev_heap_xa); + drm_mm_takedown(&client->dev_heap_mm); mutex_destroy(&client->mm_lock); mmdrop(client->mm); diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.h b/drivers/accel/amdxdna/amdxdna_pci_drv.h index 471b72299aee..34271c14d359 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.h +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.h @@ -7,6 +7,7 @@ #define _AMDXDNA_PCI_DRV_H_ #include +#include #include #include #include @@ -61,6 +62,7 @@ struct amdxdna_dev_ops { void (*hwctx_fini)(struct amdxdna_hwctx *hwctx); int (*hwctx_config)(struct amdxdna_hwctx *hwctx, u32 type, u64 value, void *buf, u32 size); int (*hwctx_sync_debug_bo)(struct amdxdna_hwctx *hwctx, u32 debug_bo_hdl); + int (*hwctx_heap_expand)(struct amdxdna_hwctx *hwctx, struct amdxdna_gem_obj *heap); void (*hmm_invalidate)(struct amdxdna_gem_obj *abo, unsigned long cur_seq); int (*cmd_submit)(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job, u64 *seq); int (*cmd_wait)(struct amdxdna_hwctx *hwctx, u64 seq, u32 timeout); @@ -95,6 +97,7 @@ struct amdxdna_dev_info { size_t dev_mem_size; const char *default_vbnv; const struct amdxdna_rev_vbnv *rev_vbnv_tbl; + size_t dev_heap_max_size; const struct amdxdna_dev_priv *dev_priv; const struct amdxdna_fw_feature_tbl *fw_feature_tbl; const struct amdxdna_dev_ops *ops; @@ -153,7 +156,10 @@ struct amdxdna_client { struct drm_file *filp; struct mutex mm_lock; /* protect memory related */ - struct amdxdna_gem_obj *dev_heap; + struct xarray dev_heap_xa; + struct drm_mm dev_heap_mm; + u32 dev_heap_nid; + size_t total_heap_size; struct iommu_sva *sva; int pasid; diff --git a/drivers/accel/amdxdna/npu1_regs.c b/drivers/accel/amdxdna/npu1_regs.c index 4e48c030a69f..ca779674017a 100644 --- a/drivers/accel/amdxdna/npu1_regs.c +++ b/drivers/accel/amdxdna/npu1_regs.c @@ -139,6 +139,7 @@ const struct amdxdna_dev_info dev_npu1_info = { .dev_mem_base = AIE2_DEVM_BASE, .dev_mem_size = AIE2_DEVM_SIZE, .default_vbnv = "RyzenAI-npu1", + .dev_heap_max_size = AIE2_DEVM_SIZE, .device_type = AMDXDNA_DEV_TYPE_KMQ, .dev_priv = &npu1_dev_priv, .fw_feature_tbl = npu1_fw_feature_table, diff --git a/drivers/accel/amdxdna/npu4_regs.c b/drivers/accel/amdxdna/npu4_regs.c index eddc31803a50..15a161384625 100644 --- a/drivers/accel/amdxdna/npu4_regs.c +++ b/drivers/accel/amdxdna/npu4_regs.c @@ -98,6 +98,7 @@ const struct amdxdna_fw_feature_tbl npu4_fw_feature_table[] = { { .features = BIT_U64(AIE2_NPU_COMMAND), .major = 6, .min_minor = 15 }, { .features = BIT_U64(AIE2_UPDATE_PROPERTY), .major = 6, .min_minor = 15 }, { .features = BIT_U64(AIE2_APP_HEALTH), .major = 6, .min_minor = 18 }, + { .features = BIT_U64(AIE2_ADD_HOST_BUFFER), .major = 6, .min_minor = 18 }, { .features = BIT_U64(AIE2_GET_DEV_REVISION), .major = 6, .min_minor = 24 }, { .features = AIE2_ALL_FEATURES, .major = 7 }, { 0 } @@ -200,6 +201,7 @@ const struct amdxdna_dev_info dev_npu4_info = { .dev_mem_base = AIE2_DEVM_BASE, .dev_mem_size = AIE2_DEVM_SIZE, .default_vbnv = "RyzenAI-npu4", + .dev_heap_max_size = AIE2_DEVM_MAX_SIZE, .device_type = AMDXDNA_DEV_TYPE_KMQ, .rev_vbnv_tbl = npu4_rev_vbnv_tbl, .dev_priv = &npu4_dev_priv, diff --git a/drivers/accel/amdxdna/npu5_regs.c b/drivers/accel/amdxdna/npu5_regs.c index a9102978e4a8..306b359d0cd3 100644 --- a/drivers/accel/amdxdna/npu5_regs.c +++ b/drivers/accel/amdxdna/npu5_regs.c @@ -107,6 +107,7 @@ const struct amdxdna_dev_info dev_npu5_info = { .dev_mem_base = AIE2_DEVM_BASE, .dev_mem_size = AIE2_DEVM_SIZE, .default_vbnv = "RyzenAI-npu5", + .dev_heap_max_size = AIE2_DEVM_MAX_SIZE, .device_type = AMDXDNA_DEV_TYPE_KMQ, .rev_vbnv_tbl = npu4_rev_vbnv_tbl, .dev_priv = &npu5_dev_priv, diff --git a/drivers/accel/amdxdna/npu6_regs.c b/drivers/accel/amdxdna/npu6_regs.c index e0db3a09740b..e68637d2a228 100644 --- a/drivers/accel/amdxdna/npu6_regs.c +++ b/drivers/accel/amdxdna/npu6_regs.c @@ -108,6 +108,7 @@ const struct amdxdna_dev_info dev_npu6_info = { .dev_mem_base = AIE2_DEVM_BASE, .dev_mem_size = AIE2_DEVM_SIZE, .default_vbnv = "RyzenAI-npu6", + .dev_heap_max_size = AIE2_DEVM_MAX_SIZE, .device_type = AMDXDNA_DEV_TYPE_KMQ, .rev_vbnv_tbl = npu4_rev_vbnv_tbl, .dev_priv = &npu6_dev_priv, From b11a6cea91f2cec688936d591548bc5c1540c648 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Thu, 30 Apr 2026 21:49:43 +0200 Subject: [PATCH 07/38] drm/bridge: chipone-icn6211: use devm_drm_bridge_add in dsi probe Use devm_drm_bridge_add() so the bridge is released if probe fails after registration, and drop drm_bridge_remove() in chipone_dsi_probe. Signed-off-by: Osama Abdelkader Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260430194944.78119-2-osama.abdelkader@gmail.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/chipone-icn6211.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/bridge/chipone-icn6211.c b/drivers/gpu/drm/bridge/chipone-icn6211.c index 08775381b075..e5957917ad88 100644 --- a/drivers/gpu/drm/bridge/chipone-icn6211.c +++ b/drivers/gpu/drm/bridge/chipone-icn6211.c @@ -730,13 +730,11 @@ static int chipone_dsi_probe(struct mipi_dsi_device *dsi) mipi_dsi_set_drvdata(dsi, icn); - drm_bridge_add(&icn->bridge); - - ret = chipone_dsi_attach(icn); + ret = devm_drm_bridge_add(dev, &icn->bridge); if (ret) - drm_bridge_remove(&icn->bridge); + return ret; - return ret; + return chipone_dsi_attach(icn); } static int chipone_i2c_probe(struct i2c_client *client) @@ -765,10 +763,7 @@ static int chipone_i2c_probe(struct i2c_client *client) static void chipone_dsi_remove(struct mipi_dsi_device *dsi) { - struct chipone *icn = mipi_dsi_get_drvdata(dsi); - mipi_dsi_detach(dsi); - drm_bridge_remove(&icn->bridge); } static const struct of_device_id chipone_of_match[] = { From 55bb2b137b6d72b3281f70045d9ee5a087df0aa4 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:05 +0200 Subject: [PATCH 08/38] drm/bridge: drm_bridge_put(): ignore ERR_PTR Most functions returning a struct drm_bridge pointer currently return a valid pointer or NULL, but this restricts their ability to return an error code as an ERR_PTR describing the error kind. In preparation to have new APIs that can return a struct drm_bridge pointer holding an ERR_PTR (and for those which already do) make drm_bridge_put() ignore ERR_PTR values, just like it ignores NULL pointers. This will avoid annoying error checking in many places and the risk of missing error checks. Suggested-by: Laurent Pinchart Link: https://lore.kernel.org/all/20260318152533.GA633439@killaraus.ideasonboard.com/ Suggested-by: Dmitry Baryshkov Link: https://lore.kernel.org/all/omlnswxukeqgnatzdvooaashgkfcacjevkvbkm6xt33itgua2k@jcmzll2w6kdq/ Reviewed-by: Dmitry Baryshkov Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-1-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index ce180f0b26b2..b46c01db8d83 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -300,7 +300,7 @@ EXPORT_SYMBOL(drm_bridge_get); /** * drm_bridge_put - Release a bridge reference - * @bridge: DRM bridge; if NULL this function does nothing + * @bridge: DRM bridge; if NULL or an ERR_PTR this function does nothing * * This function decrements the bridge's reference count and frees the * object if the reference count drops to zero. @@ -310,7 +310,7 @@ EXPORT_SYMBOL(drm_bridge_get); */ void drm_bridge_put(struct drm_bridge *bridge) { - if (bridge) + if (!IS_ERR_OR_NULL(bridge)) kref_put(&bridge->refcount, __drm_bridge_free); } EXPORT_SYMBOL(drm_bridge_put); From 03d1078112fddd706b2c1e4a7d98cf18700eb5df Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:06 +0200 Subject: [PATCH 09/38] drm/bridge: add of_drm_get_bridge_by_endpoint() drm_of_find_panel_or_bridge() is widely used, but many callers pass NULL into the @panel or the @bridge arguments, thus making a very partial usage of this rather complex function. Besides, the bridge returned in @bridge is not refcounted, thus making this API unsafe when DRM bridge hotplug will be introduced. Solve both issues for the cases of calls to drm_of_find_panel_or_bridge() with a NULL @panel pointer by adding a new function that only looks for bridges (and is thus much simpler) and increments the refcount of the returned bridge. The new function is identical to drm_of_find_panel_or_bridge() except it: - handles bridge refcounting: uses of_drm_find_and_get_bridge() instead of of_drm_find_bridge() internally to return a refcounted bridge - is simpler to use: just takes no @panel parameter, returns the pointer in the return value instead of a double pointer argument - has a simpler implementation: it is equal to drm_of_find_panel_or_bridge() after removing the code that becomes dead when @panel == NULL Also add this function to drm_bridge.c and not drm_of.c because it returns bridges only. Reviewed-by: Dmitry Baryshkov Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-2-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 41 ++++++++++++++++++++++++++++++++++++ include/drm/drm_bridge.h | 7 ++++++ 2 files changed, 48 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index b46c01db8d83..687b36eea0c7 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1582,6 +1582,47 @@ struct drm_bridge *of_drm_find_bridge(struct device_node *np) return bridge; } EXPORT_SYMBOL(of_drm_find_bridge); + +/** + * of_drm_get_bridge_by_endpoint - return DRM bridge connected to a port/endpoint + * @np: device tree node containing output ports + * @port: port in the device tree node, or -1 for the first port found + * @endpoint: endpoint in the device tree node, or -1 for the first endpoint found + * + * Given a DT node's port and endpoint number, find the connected node and + * return the associated drm_bridge device. + * + * The refcount of the returned bridge is incremented. Use drm_bridge_put() + * when done with it. + * + * Returns a pointer to the connected drm_bridge, or a negative error on failure + */ +struct drm_bridge *of_drm_get_bridge_by_endpoint(const struct device_node *np, + int port, int endpoint) +{ + struct drm_bridge *bridge; + + /* + * of_graph_get_remote_node() produces a noisy error message if port + * node isn't found and the absence of the port is a legit case here, + * so at first we silently check whether graph is present in the + * device-tree node. + */ + if (!of_graph_is_present(np)) + return ERR_PTR(-ENODEV); + + struct device_node *remote __free(device_node) = + of_graph_get_remote_node(np, port, endpoint); + if (!remote) + return ERR_PTR(-ENODEV); + + bridge = of_drm_find_and_get_bridge(remote); + if (!bridge) + return ERR_PTR(-EPROBE_DEFER); + + return bridge; +} +EXPORT_SYMBOL_GPL(of_drm_get_bridge_by_endpoint); #endif /** diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index fdac9a526f38..4ba3a5deef9a 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1327,6 +1327,8 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge, #ifdef CONFIG_OF struct drm_bridge *of_drm_find_and_get_bridge(struct device_node *np); struct drm_bridge *of_drm_find_bridge(struct device_node *np); +struct drm_bridge *of_drm_get_bridge_by_endpoint(const struct device_node *np, + int port, int endpoint); #else static inline struct drm_bridge *of_drm_find_and_get_bridge(struct device_node *np) { @@ -1336,6 +1338,11 @@ static inline struct drm_bridge *of_drm_find_bridge(struct device_node *np) { return NULL; } +static inline struct drm_bridge *of_drm_get_bridge_by_endpoint(const struct device_node *np, + int port, int endpoint) +{ + return ERR_PTR(-ENODEV); +} #endif static inline bool drm_bridge_is_last(struct drm_bridge *bridge) From 5e6016294c16f8975391497037b207ad5d2a71eb Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:07 +0200 Subject: [PATCH 10/38] drm/msm/hdmi: switch to of_drm_get_bridge_by_endpoint() This driver calls drm_of_find_panel_or_bridge() with a NULL pointer in the @panel parameter, thus using a reduced feature set of that function. Replace this call with the simpler of_drm_get_bridge_by_endpoint(). Since of_drm_get_bridge_by_endpoint() increases the refcount of the returned bridge, ensure it is put on removal. Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-3-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/msm/hdmi/hdmi.c | 70 ++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c index d9491aac1a89..474006084633 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi.c @@ -285,19 +285,27 @@ static int msm_hdmi_dev_probe(struct platform_device *pdev) spin_lock_init(&hdmi->reg_lock); mutex_init(&hdmi->state_mutex); - ret = drm_of_find_panel_or_bridge(dev_of_node(dev), 1, 0, NULL, &hdmi->next_bridge); - if (ret && ret != -ENODEV) - return ret; + hdmi->next_bridge = of_drm_get_bridge_by_endpoint(dev_of_node(dev), 1, 0); + if (IS_ERR(hdmi->next_bridge)) { + if (PTR_ERR(hdmi->next_bridge) != -ENODEV) + return PTR_ERR(hdmi->next_bridge); + + hdmi->next_bridge = NULL; + } hdmi->mmio = msm_ioremap(pdev, "core_physical"); - if (IS_ERR(hdmi->mmio)) - return PTR_ERR(hdmi->mmio); + if (IS_ERR(hdmi->mmio)) { + ret = PTR_ERR(hdmi->mmio); + goto err_put_bridge; + } /* HDCP needs physical address of hdmi register */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core_physical"); - if (!res) - return -EINVAL; + if (!res) { + ret = -EINVAL; + goto err_put_bridge; + } hdmi->mmio_phy_addr = res->start; hdmi->qfprom_mmio = msm_ioremap(pdev, "qfprom_physical"); @@ -307,45 +315,58 @@ static int msm_hdmi_dev_probe(struct platform_device *pdev) } hdmi->irq = platform_get_irq(pdev, 0); - if (hdmi->irq < 0) - return hdmi->irq; + if (hdmi->irq < 0) { + ret = hdmi->irq; + goto err_put_bridge; + } hdmi->pwr_regs = devm_kcalloc(dev, config->pwr_reg_cnt, sizeof(hdmi->pwr_regs[0]), GFP_KERNEL); - if (!hdmi->pwr_regs) - return -ENOMEM; + if (!hdmi->pwr_regs) { + ret = -ENOMEM; + goto err_put_bridge; + } for (i = 0; i < config->pwr_reg_cnt; i++) hdmi->pwr_regs[i].supply = config->pwr_reg_names[i]; ret = devm_regulator_bulk_get(dev, config->pwr_reg_cnt, hdmi->pwr_regs); - if (ret) - return dev_err_probe(dev, ret, "failed to get pwr regulators\n"); + if (ret) { + dev_err_probe(dev, ret, "failed to get pwr regulators\n"); + goto err_put_bridge; + } hdmi->pwr_clks = devm_kcalloc(dev, config->pwr_clk_cnt, sizeof(hdmi->pwr_clks[0]), GFP_KERNEL); - if (!hdmi->pwr_clks) - return -ENOMEM; + if (!hdmi->pwr_clks) { + ret = -ENOMEM; + goto err_put_bridge; + } for (i = 0; i < config->pwr_clk_cnt; i++) hdmi->pwr_clks[i].id = config->pwr_clk_names[i]; ret = devm_clk_bulk_get(dev, config->pwr_clk_cnt, hdmi->pwr_clks); if (ret) - return ret; + goto err_put_bridge; + hdmi->extp_clk = devm_clk_get_optional(dev, "extp"); - if (IS_ERR(hdmi->extp_clk)) - return dev_err_probe(dev, PTR_ERR(hdmi->extp_clk), - "failed to get extp clock\n"); + if (IS_ERR(hdmi->extp_clk)) { + ret = dev_err_probe(dev, PTR_ERR(hdmi->extp_clk), + "failed to get extp clock\n"); + goto err_put_bridge; + } hdmi->hpd_gpiod = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN); /* This will catch e.g. -EPROBE_DEFER */ - if (IS_ERR(hdmi->hpd_gpiod)) - return dev_err_probe(dev, PTR_ERR(hdmi->hpd_gpiod), - "failed to get hpd gpio\n"); + if (IS_ERR(hdmi->hpd_gpiod)) { + ret = dev_err_probe(dev, PTR_ERR(hdmi->hpd_gpiod), + "failed to get hpd gpio\n"); + goto err_put_bridge; + } if (!hdmi->hpd_gpiod) DBG("failed to get HPD gpio"); @@ -355,7 +376,7 @@ static int msm_hdmi_dev_probe(struct platform_device *pdev) ret = msm_hdmi_get_phy(hdmi); if (ret) - return ret; + goto err_put_bridge; ret = devm_pm_runtime_enable(dev); if (ret) @@ -371,6 +392,8 @@ static int msm_hdmi_dev_probe(struct platform_device *pdev) err_put_phy: msm_hdmi_put_phy(hdmi); +err_put_bridge: + drm_bridge_put(hdmi->next_bridge); return ret; } @@ -381,6 +404,7 @@ static void msm_hdmi_dev_remove(struct platform_device *pdev) component_del(&pdev->dev, &msm_hdmi_ops); msm_hdmi_put_phy(hdmi); + drm_bridge_put(hdmi->next_bridge); } static int msm_hdmi_runtime_suspend(struct device *dev) From f85a3590e31ac7d78dbb62ed617d50954f5ffde6 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:08 +0200 Subject: [PATCH 11/38] drm/hisilicon/kirin: switch to of_drm_get_bridge_by_endpoint() This driver calls drm_of_find_panel_or_bridge() with a NULL pointer in the @panel parameter, thus using a reduced feature set of that function. Replace this call with the simpler of_drm_get_bridge_by_endpoint(). Since of_drm_get_bridge_by_endpoint() increases the refcount of the returned bridge, ensure it is put on removal. Here the bridge pointer is only stored in a temporary variable, so a cleanup action is enough. Reviewed-by: Louis Chauvet Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-4-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c index e80debdc4176..ab3cd309505a 100644 --- a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c +++ b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c @@ -778,17 +778,16 @@ static int dsi_host_init(struct device *dev, struct dw_dsi *dsi) static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi) { struct drm_encoder *encoder = &dsi->encoder; - struct drm_bridge *bridge; + struct drm_bridge *bridge __free(drm_bridge_put) = NULL; struct device_node *np = dsi->dev->of_node; - int ret; /* * Get the endpoint node. In our case, dsi has one output port1 * to which the external HDMI bridge is connected. */ - ret = drm_of_find_panel_or_bridge(np, 1, 0, NULL, &bridge); - if (ret) - return ret; + bridge = of_drm_get_bridge_by_endpoint(np, 1, 0); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); /* associate the bridge to dsi encoder */ return drm_bridge_attach(encoder, bridge, NULL, 0); From 7783fc059457c41f641b490e6d0d23c33c8fa89b Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:09 +0200 Subject: [PATCH 12/38] drm/bridge: chrontel-ch7033: switch to of_drm_get_bridge_by_endpoint() This driver calls drm_of_find_panel_or_bridge() with a NULL pointer in the @panel parameter, thus using a reduced feature set of that function. Replace this call with the simpler of_drm_get_bridge_by_endpoint(). Since of_drm_get_bridge_by_endpoint() increases the refcount of the returned bridge, ensure it is put on removal. To achieve this, instead of adding an explicit drm_bridge_put(), migrate to the bridge::next_bridge pointer which is automatically put when the bridge is eventually freed. Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-5-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/chrontel-ch7033.c | 28 +++++++++++------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/bridge/chrontel-ch7033.c b/drivers/gpu/drm/bridge/chrontel-ch7033.c index 54d49d4882c8..a237c65ebd69 100644 --- a/drivers/gpu/drm/bridge/chrontel-ch7033.c +++ b/drivers/gpu/drm/bridge/chrontel-ch7033.c @@ -199,7 +199,6 @@ enum { struct ch7033_priv { struct regmap *regmap; - struct drm_bridge *next_bridge; struct drm_bridge bridge; struct drm_connector connector; }; @@ -215,7 +214,7 @@ static enum drm_connector_status ch7033_connector_detect( { struct ch7033_priv *priv = conn_to_ch7033_priv(connector); - return drm_bridge_detect(priv->next_bridge, connector); + return drm_bridge_detect(priv->bridge.next_bridge, connector); } static const struct drm_connector_funcs ch7033_connector_funcs = { @@ -233,7 +232,7 @@ static int ch7033_connector_get_modes(struct drm_connector *connector) const struct drm_edid *drm_edid; int ret; - drm_edid = drm_bridge_edid_read(priv->next_bridge, connector); + drm_edid = drm_bridge_edid_read(priv->bridge.next_bridge, connector); drm_edid_connector_update(connector, drm_edid); if (drm_edid) { ret = drm_edid_connector_add_modes(connector); @@ -275,7 +274,7 @@ static int ch7033_bridge_attach(struct drm_bridge *bridge, struct drm_connector *connector = &priv->connector; int ret; - ret = drm_bridge_attach(encoder, priv->next_bridge, bridge, + ret = drm_bridge_attach(encoder, priv->bridge.next_bridge, bridge, DRM_BRIDGE_ATTACH_NO_CONNECTOR); if (ret) return ret; @@ -283,15 +282,15 @@ static int ch7033_bridge_attach(struct drm_bridge *bridge, if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) return 0; - if (priv->next_bridge->ops & DRM_BRIDGE_OP_DETECT) { + if (priv->bridge.next_bridge->ops & DRM_BRIDGE_OP_DETECT) { connector->polled = DRM_CONNECTOR_POLL_HPD; } else { connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; } - if (priv->next_bridge->ops & DRM_BRIDGE_OP_HPD) { - drm_bridge_hpd_enable(priv->next_bridge, ch7033_hpd_event, + if (priv->bridge.next_bridge->ops & DRM_BRIDGE_OP_HPD) { + drm_bridge_hpd_enable(priv->bridge.next_bridge, ch7033_hpd_event, priv); } @@ -299,8 +298,8 @@ static int ch7033_bridge_attach(struct drm_bridge *bridge, &ch7033_connector_helper_funcs); ret = drm_connector_init_with_ddc(bridge->dev, &priv->connector, &ch7033_connector_funcs, - priv->next_bridge->type, - priv->next_bridge->ddc); + priv->bridge.next_bridge->type, + priv->bridge.next_bridge->ddc); if (ret) { DRM_ERROR("Failed to initialize connector\n"); return ret; @@ -313,8 +312,8 @@ static void ch7033_bridge_detach(struct drm_bridge *bridge) { struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); - if (priv->next_bridge->ops & DRM_BRIDGE_OP_HPD) - drm_bridge_hpd_disable(priv->next_bridge); + if (priv->bridge.next_bridge->ops & DRM_BRIDGE_OP_HPD) + drm_bridge_hpd_disable(priv->bridge.next_bridge); drm_connector_cleanup(&priv->connector); } @@ -543,10 +542,9 @@ static int ch7033_probe(struct i2c_client *client) dev_set_drvdata(dev, priv); - ret = drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, - &priv->next_bridge); - if (ret) - return ret; + priv->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, -1); + if (IS_ERR(priv->bridge.next_bridge)) + return PTR_ERR(priv->bridge.next_bridge); priv->regmap = devm_regmap_init_i2c(client, &ch7033_regmap_config); if (IS_ERR(priv->regmap)) { From 91b42aa55c7777545e528bbd991fadea5c561d46 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:10 +0200 Subject: [PATCH 13/38] drm/bridge: lontium-lt9611uxc: switch to of_drm_get_bridge_by_endpoint() This driver calls drm_of_find_panel_or_bridge() with a NULL pointer in the @panel parameter, thus using a reduced feature set of that function. Replace this call with the simpler of_drm_get_bridge_by_endpoint(). Since of_drm_get_bridge_by_endpoint() increases the refcount of the returned bridge, ensure it is put on removal. To achieve this, instead of adding an explicit drm_bridge_put(), migrate to the bridge::next_bridge pointer which is automatically put when the bridge is eventually freed. Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-6-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 11aab07d88df..9427cc2358ae 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -35,7 +35,6 @@ struct lt9611uxc { struct device *dev; struct drm_bridge bridge; - struct drm_bridge *next_bridge; struct regmap *regmap; /* Protects all accesses to registers by stopping the on-chip MCU */ @@ -284,7 +283,7 @@ static int lt9611uxc_bridge_attach(struct drm_bridge *bridge, { struct lt9611uxc *lt9611uxc = bridge_to_lt9611uxc(bridge); - return drm_bridge_attach(encoder, lt9611uxc->next_bridge, + return drm_bridge_attach(encoder, lt9611uxc->bridge.next_bridge, bridge, flags); } @@ -487,7 +486,11 @@ static int lt9611uxc_parse_dt(struct device *dev, lt9611uxc->dsi1_node = of_graph_get_remote_node(dev->of_node, 1, -1); - return drm_of_find_panel_or_bridge(dev->of_node, 2, -1, NULL, <9611uxc->next_bridge); + lt9611uxc->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 2, -1); + if (IS_ERR(lt9611uxc->bridge.next_bridge)) + return PTR_ERR(lt9611uxc->bridge.next_bridge); + + return 0; } static int lt9611uxc_gpio_init(struct lt9611uxc *lt9611uxc) From cd9517f6e2093a4579f8a37ca66214eb34e9cd04 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:11 +0200 Subject: [PATCH 14/38] drm/bridge: lt9611: switch to of_drm_get_bridge_by_endpoint() This driver calls drm_of_find_panel_or_bridge() with a NULL pointer in the @panel parameter, thus using a reduced feature set of that function. Replace this call with the simpler of_drm_get_bridge_by_endpoint(). Since of_drm_get_bridge_by_endpoint() increases the refcount of the returned bridge, ensure it is put on removal. To achieve this, instead of adding an explicit drm_bridge_put(), migrate to the bridge::next_bridge pointer which is automatically put when the bridge is eventually freed. Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-7-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/lontium-lt9611.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611.c b/drivers/gpu/drm/bridge/lontium-lt9611.c index 0f49b13193b9..21305296e111 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611.c @@ -37,7 +37,6 @@ struct lt9611 { struct device *dev; struct drm_bridge bridge; - struct drm_bridge *next_bridge; struct regmap *regmap; @@ -761,7 +760,7 @@ static int lt9611_bridge_attach(struct drm_bridge *bridge, { struct lt9611 *lt9611 = bridge_to_lt9611(bridge); - return drm_bridge_attach(encoder, lt9611->next_bridge, + return drm_bridge_attach(encoder, lt9611->bridge.next_bridge, bridge, flags); } @@ -1058,7 +1057,11 @@ static int lt9611_parse_dt(struct device *dev, lt9611->ac_mode = of_property_read_bool(dev->of_node, "lt,ac-mode"); - return drm_of_find_panel_or_bridge(dev->of_node, 2, -1, NULL, <9611->next_bridge); + lt9611->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 2, -1); + if (IS_ERR(lt9611->bridge.next_bridge)) + return PTR_ERR(lt9611->bridge.next_bridge); + + return 0; } static int lt9611_gpio_init(struct lt9611 *lt9611) From f08eff4ad2873960d03a3834c04b86bfa4cfcdcb Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:12 +0200 Subject: [PATCH 15/38] drm/bridge: adv7511: switch to of_drm_get_bridge_by_endpoint() This driver calls drm_of_find_panel_or_bridge() with a NULL pointer in the @panel parameter, thus using a reduced feature set of that function. Replace this call with the simpler of_drm_get_bridge_by_endpoint(). Since of_drm_get_bridge_by_endpoint() increases the refcount of the returned bridge, ensure it is put on removal. To achieve this, instead of adding an explicit drm_bridge_put(), migrate to the bridge::next_bridge pointer which is automatically put when the bridge is eventually freed. Tested-by: Biju Das Reviewed-by: Dmitry Baryshkov Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-8-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/adv7511/adv7511.h | 1 - drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index 8be7266fd4f4..12c95198d9a4 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -354,7 +354,6 @@ struct adv7511 { enum drm_connector_status status; bool powered; - struct drm_bridge *next_bridge; struct drm_display_mode curr_mode; unsigned int f_tmds; diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 821a3008a1e7..02f8f7e78a16 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -849,8 +849,8 @@ static int adv7511_bridge_attach(struct drm_bridge *bridge, struct adv7511 *adv = bridge_to_adv7511(bridge); int ret = 0; - if (adv->next_bridge) { - ret = drm_bridge_attach(encoder, adv->next_bridge, bridge, + if (adv->bridge.next_bridge) { + ret = drm_bridge_attach(encoder, adv->bridge.next_bridge, bridge, flags | DRM_BRIDGE_ATTACH_NO_CONNECTOR); if (ret) return ret; @@ -1249,10 +1249,13 @@ static int adv7511_probe(struct i2c_client *i2c) memset(&link_config, 0, sizeof(link_config)); - ret = drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, - &adv7511->next_bridge); - if (ret && ret != -ENODEV) - return ret; + adv7511->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, -1); + if (IS_ERR(adv7511->bridge.next_bridge)) { + if (PTR_ERR(adv7511->bridge.next_bridge) == -ENODEV) + adv7511->bridge.next_bridge = NULL; + else + return PTR_ERR(adv7511->bridge.next_bridge); + } if (adv7511->info->link_config) ret = adv7511_parse_dt(dev->of_node, &link_config); From 898a5e07f7e39d771910fcd9ba73ba6e533f9283 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:13 +0200 Subject: [PATCH 16/38] drm/bridge: lt8713sx: switch to of_drm_get_bridge_by_endpoint() This driver calls drm_of_find_panel_or_bridge() with a NULL pointer in the @panel parameter, thus using a reduced feature set of that function. Replace this call with the simpler of_drm_get_bridge_by_endpoint(). Since of_drm_get_bridge_by_endpoint() increases the refcount of the returned bridge, ensure it is put on removal. To achieve this, instead of adding an explicit drm_bridge_put(), migrate to the bridge::next_bridge pointer which is automatically put when the bridge is eventually freed. Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-9-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/lontium-lt8713sx.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt8713sx.c b/drivers/gpu/drm/bridge/lontium-lt8713sx.c index 18fac6a46db4..cee485adf5e5 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8713sx.c +++ b/drivers/gpu/drm/bridge/lontium-lt8713sx.c @@ -32,7 +32,6 @@ DECLARE_CRC8_TABLE(lt8713sx_crc_table); struct lt8713sx { struct device *dev; struct drm_bridge bridge; - struct drm_bridge *next_bridge; struct regmap *regmap; /* Protects all accesses to registers by stopping the on-chip MCU */ @@ -458,7 +457,7 @@ static int lt8713sx_bridge_attach(struct drm_bridge *bridge, struct lt8713sx *lt8713sx = container_of(bridge, struct lt8713sx, bridge); return drm_bridge_attach(encoder, - lt8713sx->next_bridge, + lt8713sx->bridge.next_bridge, bridge, flags); } @@ -537,10 +536,9 @@ static int lt8713sx_probe(struct i2c_client *client) if (IS_ERR(lt8713sx->regmap)) return dev_err_probe(dev, PTR_ERR(lt8713sx->regmap), "regmap i2c init failed\n"); - ret = drm_of_find_panel_or_bridge(lt8713sx->dev->of_node, 1, -1, NULL, - <8713sx->next_bridge); - if (ret < 0) - return ret; + lt8713sx->bridge.next_bridge = of_drm_get_bridge_by_endpoint(lt8713sx->dev->of_node, 1, -1); + if (IS_ERR(lt8713sx->bridge.next_bridge)) + return PTR_ERR(lt8713sx->bridge.next_bridge); ret = lt8713sx_gpio_init(lt8713sx); if (ret < 0) From 62f1a79e1b004b250e68e10c32a29fd954fc9725 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:14 +0200 Subject: [PATCH 17/38] drm: zynqmp_dp: switch to of_drm_get_bridge_by_endpoint() This driver calls drm_of_find_panel_or_bridge() with a NULL pointer in the @panel parameter, thus using a reduced feature set of that function. Replace this call with the simpler of_drm_get_bridge_by_endpoint(). Since of_drm_get_bridge_by_endpoint() increases the refcount of the returned bridge, ensure it is put on removal. To achieve this, instead of adding an explicit drm_bridge_put(), migrate to the bridge::next_bridge pointer which is automatically put when the bridge is eventually freed. Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-10-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 05bd31fe675b..7fb11b0a44f0 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -353,7 +353,6 @@ struct zynqmp_dp_train_set_priv { * @lock: Mutex protecting this struct and register access (but not AUX) * @irq: irq * @bridge: DRM bridge for the DP encoder - * @next_bridge: The downstream bridge * @test: Configuration for test mode * @config: IP core configuration from DTS * @aux: aux channel @@ -385,7 +384,6 @@ struct zynqmp_dp { struct completion aux_done; struct mutex lock; - struct drm_bridge *next_bridge; struct device *dev; struct zynqmp_dpsub *dpsub; void __iomem *iomem; @@ -1494,8 +1492,8 @@ static int zynqmp_dp_bridge_attach(struct drm_bridge *bridge, return ret; } - if (dp->next_bridge) { - ret = drm_bridge_attach(encoder, dp->next_bridge, + if (dp->bridge.next_bridge) { + ret = drm_bridge_attach(encoder, dp->bridge.next_bridge, bridge, flags); if (ret < 0) goto error; @@ -2461,10 +2459,15 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub) * Acquire the next bridge in the chain. Ignore errors caused by port@5 * not being connected for backward-compatibility with older DTs. */ - ret = drm_of_find_panel_or_bridge(dp->dev->of_node, 5, 0, NULL, - &dp->next_bridge); - if (ret < 0 && ret != -ENODEV) - goto err_reset; + dp->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dp->dev->of_node, 5, 0); + if (IS_ERR(dp->bridge.next_bridge)) { + if (PTR_ERR(dp->bridge.next_bridge) != -ENODEV) { + ret = PTR_ERR(dp->bridge.next_bridge); + goto err_reset; + } + + dp->bridge.next_bridge = NULL; + } /* Initialize the hardware. */ dp->config.misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK; From 0d5da248ea2d7802a5055ab6762340ac870bd0ee Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 11 May 2026 18:40:15 +0200 Subject: [PATCH 18/38] drm: of: forbid bridge-only calls to drm_of_find_panel_or_bridge() Up to now drm_of_find_panel_or_bridge() can be called with a bridge pointer only, a panel pointer only, or both a bridge and a panel pointers. The logic to handle all the three cases is somewhat complex to read however. Now all bridge-only callers have been converted to of_drm_get_bridge_by_endpoint(), which is simpler and handles bridge refcounting. So forbid new bridge-only users by mandating a non-NULL panel pointer in the docs and in the sanity checks along with a warning. Reviewed-by: Dmitry Baryshkov Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260511-drm-bridge-alloc-getput-panel_or_bridge-v6-11-f61c9e498b3f@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_of.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c index ef6b09316963..d03ada82eac9 100644 --- a/drivers/gpu/drm/drm_of.c +++ b/drivers/gpu/drm/drm_of.c @@ -225,15 +225,15 @@ EXPORT_SYMBOL_GPL(drm_of_encoder_active_endpoint); * @np: device tree node containing encoder output ports * @port: port in the device tree node * @endpoint: endpoint in the device tree node - * @panel: pointer to hold returned drm_panel + * @panel: pointer to hold returned drm_panel, must not be NULL * @bridge: pointer to hold returned drm_bridge * * Given a DT node's port and endpoint number, find the connected node and - * return either the associated struct drm_panel or drm_bridge device. Either - * @panel or @bridge must not be NULL. + * return either the associated struct drm_panel or drm_bridge device. * * This function is deprecated and should not be used in new drivers. Use - * devm_drm_of_get_bridge() instead. + * of_drm_get_bridge_by_endpoint() instead when not looking for a panel, or + * devm_drm_of_get_bridge() otherwise. * * Returns zero if successful, or one of the standard error codes if it fails. */ @@ -245,10 +245,10 @@ int drm_of_find_panel_or_bridge(const struct device_node *np, int ret = -EPROBE_DEFER; struct device_node *remote; - if (!panel && !bridge) + if (WARN_ON(!panel)) return -EINVAL; - if (panel) - *panel = NULL; + + *panel = NULL; /* * of_graph_get_remote_node() produces a noisy error message if port @@ -263,13 +263,11 @@ int drm_of_find_panel_or_bridge(const struct device_node *np, if (!remote) return -ENODEV; - if (panel) { - *panel = of_drm_find_panel(remote); - if (!IS_ERR(*panel)) - ret = 0; - else - *panel = NULL; - } + *panel = of_drm_find_panel(remote); + if (!IS_ERR(*panel)) + ret = 0; + else + *panel = NULL; if (bridge) { if (ret) { From de1e8a590f4ed48b6b7902fc3aafc878262f8278 Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Tue, 19 May 2026 13:55:26 +0530 Subject: [PATCH 19/38] drm/imagination: Populate FW common context ID before passing to the FW Initialise the context ID for the FW common context correctly by moving the context allocation earlier. Signed-off-by: Brajesh Gupta Reviewed-by: Matt Coster Link: https://patch.msgid.link/20260519-b4-context_reset-v2-1-931018a7131d@imgtec.com Signed-off-by: Matt Coster --- drivers/gpu/drm/imagination/pvr_context.c | 32 ++++++++++++----------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_context.c b/drivers/gpu/drm/imagination/pvr_context.c index 8de70c30b9de..eba4694400b5 100644 --- a/drivers/gpu/drm/imagination/pvr_context.c +++ b/drivers/gpu/drm/imagination/pvr_context.c @@ -318,10 +318,14 @@ int pvr_context_create(struct pvr_file *pvr_file, struct drm_pvr_ioctl_create_co goto err_put_vm; } - err = pvr_context_create_queues(ctx, args, ctx->data); + err = xa_alloc(&pvr_dev->ctx_ids, &ctx->ctx_id, ctx, xa_limit_32b, GFP_KERNEL); if (err) goto err_free_ctx_data; + err = pvr_context_create_queues(ctx, args, ctx->data); + if (err) + goto err_free_ctx_id; + err = init_fw_objs(ctx, args, ctx->data); if (err) goto err_destroy_queues; @@ -329,22 +333,11 @@ int pvr_context_create(struct pvr_file *pvr_file, struct drm_pvr_ioctl_create_co err = pvr_fw_object_create(pvr_dev, ctx_size, PVR_BO_FW_FLAGS_DEVICE_UNCACHED, ctx_fw_data_init, ctx, &ctx->fw_obj); if (err) - goto err_free_ctx_data; - - err = xa_alloc(&pvr_dev->ctx_ids, &ctx->ctx_id, ctx, xa_limit_32b, GFP_KERNEL); - if (err) - goto err_destroy_fw_obj; + goto err_destroy_queues; err = xa_alloc(&pvr_file->ctx_handles, &args->handle, ctx, xa_limit_32b, GFP_KERNEL); - if (err) { - /* - * It's possible that another thread could have taken a reference on the context at - * this point as it is in the ctx_ids xarray. Therefore instead of directly - * destroying the context, drop a reference instead. - */ - pvr_context_put(ctx); - return err; - } + if (err) + goto err_destroy_fw_obj; spin_lock(&pvr_dev->ctx_list_lock); list_add_tail(&ctx->file_link, &pvr_file->contexts); @@ -358,6 +351,15 @@ int pvr_context_create(struct pvr_file *pvr_file, struct drm_pvr_ioctl_create_co err_destroy_queues: pvr_context_destroy_queues(ctx); +err_free_ctx_id: + /* + * Ctx_id is not exposed to userspace and not visible yet within + * the kernel/FW, plus a matching context handle (exposed to userspace) + * hasn't been allocated yet, so it is safe to remove ctx_id + * from the ctx_ids xarray. + */ + xa_erase(&pvr_dev->ctx_ids, ctx->ctx_id); + err_free_ctx_data: kfree(ctx->data); From ee7cb742531432ec1bb29b996e3363aacd860054 Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Tue, 19 May 2026 13:55:27 +0530 Subject: [PATCH 20/38] drm/imagination: Don't timeout job if its fence has been signaled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verify the job’s fence in the timeout handler; if the firmware has since signaled completion, then report NO HANG. Signed-off-by: Brajesh Gupta Reviewed-by: Matt Coster Link: https://patch.msgid.link/20260519-b4-context_reset-v2-2-931018a7131d@imgtec.com Signed-off-by: Matt Coster --- drivers/gpu/drm/imagination/pvr_queue.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/imagination/pvr_queue.c b/drivers/gpu/drm/imagination/pvr_queue.c index d10a13173f0f..822e6259674f 100644 --- a/drivers/gpu/drm/imagination/pvr_queue.c +++ b/drivers/gpu/drm/imagination/pvr_queue.c @@ -851,7 +851,9 @@ static void pvr_queue_start(struct pvr_queue *queue) * the scheduler, and re-assign parent fences in the middle. * * Return: - * * DRM_GPU_SCHED_STAT_RESET. + * *%DRM_GPU_SCHED_STAT_NO_HANG if the job fence has already been + * signaled, or + * *%DRM_GPU_SCHED_STAT_RESET otherwise. */ static enum drm_gpu_sched_stat pvr_queue_timedout_job(struct drm_sched_job *s_job) @@ -862,6 +864,9 @@ pvr_queue_timedout_job(struct drm_sched_job *s_job) struct pvr_job *job; u32 job_count = 0; + if (dma_fence_is_signaled(s_job->s_fence->parent)) + return DRM_GPU_SCHED_STAT_NO_HANG; + dev_err(sched->dev, "Job timeout\n"); /* Before we stop the scheduler, make sure the queue is out of any list, so From 42577ba79fbfbc6c2f246d523cb22a66329d4826 Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Tue, 19 May 2026 13:55:28 +0530 Subject: [PATCH 21/38] drm/imagination: Rename FW booted to FW initialised Rename the variable from 'booted' to 'initialised' to align with the driver's expectations and state, indicating that FW boot initialisation is complete and the firmware is fully initialised. Signed-off-by: Brajesh Gupta Reviewed-by: Matt Coster Link: https://patch.msgid.link/20260519-b4-context_reset-v2-3-931018a7131d@imgtec.com Signed-off-by: Matt Coster --- drivers/gpu/drm/imagination/pvr_device.c | 2 +- drivers/gpu/drm/imagination/pvr_fw.c | 4 ++-- drivers/gpu/drm/imagination/pvr_fw.h | 7 +++++-- drivers/gpu/drm/imagination/pvr_mmu.c | 4 ++-- drivers/gpu/drm/imagination/pvr_power.c | 14 +++++++------- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_device.c b/drivers/gpu/drm/imagination/pvr_device.c index dbb6f5a8ded1..49696101b547 100644 --- a/drivers/gpu/drm/imagination/pvr_device.c +++ b/drivers/gpu/drm/imagination/pvr_device.c @@ -213,7 +213,7 @@ static irqreturn_t pvr_device_irq_thread_handler(int irq, void *data) while (pvr_fw_irq_pending(pvr_dev)) { pvr_fw_irq_clear(pvr_dev); - if (pvr_dev->fw_dev.booted) { + if (pvr_dev->fw_dev.initialised) { pvr_fwccb_process(pvr_dev); pvr_kccb_wake_up_waiters(pvr_dev); pvr_device_process_active_queues(pvr_dev); diff --git a/drivers/gpu/drm/imagination/pvr_fw.c b/drivers/gpu/drm/imagination/pvr_fw.c index 288516dc2560..b8ad3f1d222c 100644 --- a/drivers/gpu/drm/imagination/pvr_fw.c +++ b/drivers/gpu/drm/imagination/pvr_fw.c @@ -1004,7 +1004,7 @@ pvr_fw_init(struct pvr_device *pvr_dev) goto err_fw_stop; } - fw_dev->booted = true; + fw_dev->initialised = true; return 0; @@ -1044,7 +1044,7 @@ pvr_fw_fini(struct pvr_device *pvr_dev) { struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; - fw_dev->booted = false; + fw_dev->initialised = false; pvr_fw_destroy_structures(pvr_dev); pvr_fw_object_unmap_and_destroy(pvr_dev->kccb.rtn_obj); diff --git a/drivers/gpu/drm/imagination/pvr_fw.h b/drivers/gpu/drm/imagination/pvr_fw.h index 1404dd492d7c..3390c84e4fd3 100644 --- a/drivers/gpu/drm/imagination/pvr_fw.h +++ b/drivers/gpu/drm/imagination/pvr_fw.h @@ -295,8 +295,11 @@ struct pvr_fw_device { /** @mem: Structure containing objects representing firmware memory allocations. */ struct pvr_fw_mem mem; - /** @booted: %true if the firmware has been booted, %false otherwise. */ - bool booted; + /** + * @initialised: %true if the firmware has been successfully initialised, + * %false otherwise. + */ + bool initialised; /** * @processor_type: FW processor type for this device. Must be one of diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index 2e4da5b2c499..e9fefcc4e234 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -133,8 +133,8 @@ int pvr_mmu_flush_exec(struct pvr_device *pvr_dev, bool wait) if (!drm_dev_enter(from_pvr_device(pvr_dev), &idx)) return -EIO; - /* Can't flush MMU if the firmware hasn't booted yet. */ - if (!pvr_dev->fw_dev.booted) + /* Can't flush MMU if the firmware hasn't been initialised yet. */ + if (!pvr_dev->fw_dev.initialised) goto err_drm_dev_exit; cmd_mmu_cache_data->cache_flags = diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c index 88fcce433d12..a73a6815306b 100644 --- a/drivers/gpu/drm/imagination/pvr_power.c +++ b/drivers/gpu/drm/imagination/pvr_power.c @@ -216,7 +216,7 @@ pvr_watchdog_worker(struct work_struct *work) if (pm_runtime_get_if_in_use(from_pvr_device(pvr_dev)->dev) <= 0) goto out_requeue; - if (!pvr_dev->fw_dev.booted) + if (!pvr_dev->fw_dev.initialised) goto out_pm_runtime_put; stalled = pvr_watchdog_kccb_stalled(pvr_dev); @@ -378,8 +378,8 @@ pvr_power_device_suspend(struct device *dev) if (!drm_dev_enter(drm_dev, &idx)) return -EIO; - if (pvr_dev->fw_dev.booted) { - err = pvr_power_fw_disable(pvr_dev, false, true); + if (pvr_dev->fw_dev.initialised) { + err = pvr_power_fw_disable(pvr_dev, false); if (err) goto err_drm_dev_exit; } @@ -408,8 +408,8 @@ pvr_power_device_resume(struct device *dev) if (err) goto err_drm_dev_exit; - if (pvr_dev->fw_dev.booted) { - err = pvr_power_fw_enable(pvr_dev, true); + if (pvr_dev->fw_dev.initialised) { + err = pvr_power_fw_enable(pvr_dev); if (err) goto err_power_off; } @@ -548,7 +548,7 @@ pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset) err = pvr_power_fw_disable(pvr_dev, hard_reset, false); if (!err) { if (hard_reset) { - pvr_dev->fw_dev.booted = false; + pvr_dev->fw_dev.initialised = false; WARN_ON(pvr_power_device_suspend(from_pvr_device(pvr_dev)->dev)); err = pvr_fw_hard_reset(pvr_dev); @@ -556,7 +556,7 @@ pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset) goto err_device_lost; err = pvr_power_device_resume(from_pvr_device(pvr_dev)->dev); - pvr_dev->fw_dev.booted = true; + pvr_dev->fw_dev.initialised = true; if (err) goto err_device_lost; } else { From fe49308cb80d24972595a6b87c516a322d22c92c Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Tue, 19 May 2026 13:55:29 +0530 Subject: [PATCH 22/38] drm/imagination: Access FW initialised state with READ/WRITE_ONCE Update FW initialised state shared resource access with READ/WRITE_ONCE to prevent following: - Non-atomic access to variable in multi-thread/CPU case. - Merge successive loads from the same variable. Signed-off-by: Brajesh Gupta Reviewed-by: Matt Coster Link: https://patch.msgid.link/20260519-b4-context_reset-v2-4-931018a7131d@imgtec.com Signed-off-by: Matt Coster --- drivers/gpu/drm/imagination/pvr_device.c | 2 +- drivers/gpu/drm/imagination/pvr_fw.c | 4 ++-- drivers/gpu/drm/imagination/pvr_mmu.c | 2 +- drivers/gpu/drm/imagination/pvr_power.c | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_device.c b/drivers/gpu/drm/imagination/pvr_device.c index 49696101b547..2691ef9af0ca 100644 --- a/drivers/gpu/drm/imagination/pvr_device.c +++ b/drivers/gpu/drm/imagination/pvr_device.c @@ -213,7 +213,7 @@ static irqreturn_t pvr_device_irq_thread_handler(int irq, void *data) while (pvr_fw_irq_pending(pvr_dev)) { pvr_fw_irq_clear(pvr_dev); - if (pvr_dev->fw_dev.initialised) { + if (READ_ONCE(pvr_dev->fw_dev.initialised)) { pvr_fwccb_process(pvr_dev); pvr_kccb_wake_up_waiters(pvr_dev); pvr_device_process_active_queues(pvr_dev); diff --git a/drivers/gpu/drm/imagination/pvr_fw.c b/drivers/gpu/drm/imagination/pvr_fw.c index b8ad3f1d222c..850a3ec8e775 100644 --- a/drivers/gpu/drm/imagination/pvr_fw.c +++ b/drivers/gpu/drm/imagination/pvr_fw.c @@ -1004,7 +1004,7 @@ pvr_fw_init(struct pvr_device *pvr_dev) goto err_fw_stop; } - fw_dev->initialised = true; + WRITE_ONCE(fw_dev->initialised, true); return 0; @@ -1044,7 +1044,7 @@ pvr_fw_fini(struct pvr_device *pvr_dev) { struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; - fw_dev->initialised = false; + WRITE_ONCE(fw_dev->initialised, false); pvr_fw_destroy_structures(pvr_dev); pvr_fw_object_unmap_and_destroy(pvr_dev->kccb.rtn_obj); diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index e9fefcc4e234..3cac482e1034 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -134,7 +134,7 @@ int pvr_mmu_flush_exec(struct pvr_device *pvr_dev, bool wait) return -EIO; /* Can't flush MMU if the firmware hasn't been initialised yet. */ - if (!pvr_dev->fw_dev.initialised) + if (!READ_ONCE(pvr_dev->fw_dev.initialised)) goto err_drm_dev_exit; cmd_mmu_cache_data->cache_flags = diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c index a73a6815306b..0ed9e7be604b 100644 --- a/drivers/gpu/drm/imagination/pvr_power.c +++ b/drivers/gpu/drm/imagination/pvr_power.c @@ -216,7 +216,7 @@ pvr_watchdog_worker(struct work_struct *work) if (pm_runtime_get_if_in_use(from_pvr_device(pvr_dev)->dev) <= 0) goto out_requeue; - if (!pvr_dev->fw_dev.initialised) + if (!READ_ONCE(pvr_dev->fw_dev.initialised)) goto out_pm_runtime_put; stalled = pvr_watchdog_kccb_stalled(pvr_dev); @@ -378,7 +378,7 @@ pvr_power_device_suspend(struct device *dev) if (!drm_dev_enter(drm_dev, &idx)) return -EIO; - if (pvr_dev->fw_dev.initialised) { + if (READ_ONCE(pvr_dev->fw_dev.initialised)) { err = pvr_power_fw_disable(pvr_dev, false); if (err) goto err_drm_dev_exit; @@ -408,7 +408,7 @@ pvr_power_device_resume(struct device *dev) if (err) goto err_drm_dev_exit; - if (pvr_dev->fw_dev.initialised) { + if (READ_ONCE(pvr_dev->fw_dev.initialised)) { err = pvr_power_fw_enable(pvr_dev); if (err) goto err_power_off; @@ -548,7 +548,7 @@ pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset) err = pvr_power_fw_disable(pvr_dev, hard_reset, false); if (!err) { if (hard_reset) { - pvr_dev->fw_dev.initialised = false; + WRITE_ONCE(pvr_dev->fw_dev.initialised, false); WARN_ON(pvr_power_device_suspend(from_pvr_device(pvr_dev)->dev)); err = pvr_fw_hard_reset(pvr_dev); @@ -556,7 +556,7 @@ pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset) goto err_device_lost; err = pvr_power_device_resume(from_pvr_device(pvr_dev)->dev); - pvr_dev->fw_dev.initialised = true; + WRITE_ONCE(pvr_dev->fw_dev.initialised, true); if (err) goto err_device_lost; } else { From c1079aebb4de218caa86c44f9a53700d1a582683 Mon Sep 17 00:00:00 2001 From: Alexandru Dadu Date: Wed, 13 May 2026 15:00:45 +0300 Subject: [PATCH 23/38] drm/imagination: Add support for trace points Add support for workload submission trace points. Co-developed-by: Donald Robson Signed-off-by: Donald Robson Signed-off-by: Alexandru Dadu Reviewed-by: Matt Coster Link: https://patch.msgid.link/20260513-b4-pvr-trace-points-v1-1-81222d1a4c99@imgtec.com Signed-off-by: Matt Coster --- drivers/gpu/drm/imagination/Makefile | 3 + drivers/gpu/drm/imagination/pvr_drv.c | 3 + drivers/gpu/drm/imagination/pvr_job.c | 3 + drivers/gpu/drm/imagination/pvr_queue.c | 5 + drivers/gpu/drm/imagination/pvr_trace.h | 113 ++++++++++++++++++ .../gpu/drm/imagination/pvr_trace_points.c | 7 ++ 6 files changed, 134 insertions(+) create mode 100644 drivers/gpu/drm/imagination/pvr_trace.h create mode 100644 drivers/gpu/drm/imagination/pvr_trace_points.c diff --git a/drivers/gpu/drm/imagination/Makefile b/drivers/gpu/drm/imagination/Makefile index 1222a14262e4..a5c5e386f16a 100644 --- a/drivers/gpu/drm/imagination/Makefile +++ b/drivers/gpu/drm/imagination/Makefile @@ -32,6 +32,9 @@ powervr-y := \ powervr-$(CONFIG_DEBUG_FS) += \ pvr_debugfs.o +powervr-$(CONFIG_TRACEPOINTS) += \ + pvr_trace_points.o + obj-$(CONFIG_DRM_POWERVR) += powervr.o obj-$(CONFIG_DRM_POWERVR_KUNIT_TEST) += pvr_test.o diff --git a/drivers/gpu/drm/imagination/pvr_drv.c b/drivers/gpu/drm/imagination/pvr_drv.c index 268900464ab6..b20c462bcba0 100644 --- a/drivers/gpu/drm/imagination/pvr_drv.c +++ b/drivers/gpu/drm/imagination/pvr_drv.c @@ -14,6 +14,7 @@ #include "pvr_rogue_defs.h" #include "pvr_rogue_fwif_client.h" #include "pvr_rogue_fwif_shared.h" +#include "pvr_trace.h" #include "pvr_vm.h" #include @@ -1150,6 +1151,8 @@ pvr_ioctl_submit_jobs(struct drm_device *drm_dev, void *raw_args, int idx; int err; + trace_pvr_job_submit_ioctl(pvr_dev, args->jobs.count); + if (!drm_dev_enter(drm_dev, &idx)) return -EIO; diff --git a/drivers/gpu/drm/imagination/pvr_job.c b/drivers/gpu/drm/imagination/pvr_job.c index dd9f5df01e08..b8a58d817009 100644 --- a/drivers/gpu/drm/imagination/pvr_job.c +++ b/drivers/gpu/drm/imagination/pvr_job.c @@ -14,6 +14,7 @@ #include "pvr_stream.h" #include "pvr_stream_defs.h" #include "pvr_sync.h" +#include "pvr_trace.h" #include #include @@ -510,6 +511,8 @@ static int pvr_job_data_init(struct pvr_device *pvr_dev, } job_data_out[i].sync_op_count = job_args[i].sync_ops.count; + + trace_pvr_job_create(pvr_dev, job_data_out[i].job, job_data_out[i].sync_op_count); } return 0; diff --git a/drivers/gpu/drm/imagination/pvr_queue.c b/drivers/gpu/drm/imagination/pvr_queue.c index 822e6259674f..7ed60e1c1a86 100644 --- a/drivers/gpu/drm/imagination/pvr_queue.c +++ b/drivers/gpu/drm/imagination/pvr_queue.c @@ -10,6 +10,7 @@ #include "pvr_drv.h" #include "pvr_job.h" #include "pvr_queue.h" +#include "pvr_trace.h" #include "pvr_vm.h" #include "pvr_rogue_fwif_client.h" @@ -725,6 +726,8 @@ static void pvr_queue_submit_job_to_cccb(struct pvr_job *job) cmd->partial_render_geom_frag_fence.value = job->done_fence->seqno - 1; } + trace_pvr_job_submit_fw(job); + /* Submit job to FW */ pvr_cccb_write_command_with_header(cccb, job->fw_ccb_cmd_type, job->cmd_len, job->cmd, job->id, job->id); @@ -1198,6 +1201,8 @@ void pvr_queue_job_cleanup(struct pvr_job *job) if (job->base.s_fence) drm_sched_job_cleanup(&job->base); + + trace_pvr_job_done(job); } /** diff --git a/drivers/gpu/drm/imagination/pvr_trace.h b/drivers/gpu/drm/imagination/pvr_trace.h new file mode 100644 index 000000000000..ffbbde3b2bcc --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_trace.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright (c) 2026 Imagination Technologies Ltd. */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM pvr + +#if !defined(PVR_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define PVR_TRACE_H + +#include "pvr_context.h" +#include "pvr_device.h" +#include "pvr_fw.h" +#include "pvr_hwrt.h" +#include "pvr_job.h" +#include "pvr_sync.h" + +#include +#include +#include + +/* + * NOTE: + * When adding trace points, or extra data to existing ones - you must capture + * all the data in the TP_fast_assign section that you wish to use in the + * TP_printk section. This is because the printk is performed on demand from the + * captured data when you `cat /sys/kernel/tracing/trace` and by the time this + * happens any pointers you captured will likely no longer point to valid data. + */ + +/* Job submit */ + +TRACE_EVENT(pvr_job_submit_ioctl, + TP_PROTO(struct pvr_device *pvr_dev, u32 count), + TP_ARGS(pvr_dev, count), + TP_STRUCT__entry(__field(struct pvr_device *, pvr_dev) + __field(u32, count)), + TP_fast_assign(__entry->pvr_dev = pvr_dev; + __entry->count = count;), + TP_printk("pvr_dev=%p count=%u", + __entry->pvr_dev, + __entry->count) +); + +#define PVR_JOB_TYPE_TO_STR(val) \ + __print_symbolic(val, \ + { DRM_PVR_JOB_TYPE_GEOMETRY, "geometry" }, \ + { DRM_PVR_JOB_TYPE_FRAGMENT, "fragment" }, \ + { DRM_PVR_JOB_TYPE_COMPUTE, "compute" }, \ + { DRM_PVR_JOB_TYPE_TRANSFER_FRAG, "transfer" }) + +TRACE_EVENT(pvr_job_create, + TP_PROTO(struct pvr_device *pvr_dev, struct pvr_job *job, + u32 sync_op_count), + TP_ARGS(pvr_dev, job, sync_op_count), + TP_STRUCT__entry(__field(struct pvr_device *, pvr_dev) + __field(struct pvr_context *, ctx) + __field(struct pvr_fw_object *, fw_obj) + __field(u32, fw_addr) + __field(u32, hwrt_addr) + __field(struct pvr_job *, job) + __field(enum drm_pvr_job_type, job_type) + __field(u32, sync_op_count)), + TP_fast_assign(__entry->pvr_dev = pvr_dev; + __entry->ctx = job->ctx; + __entry->fw_obj = job->ctx->fw_obj; + pvr_fw_object_get_fw_addr(job->ctx->fw_obj, &__entry->fw_addr); + __entry->hwrt_addr = job->hwrt ? + job->hwrt->fw_obj->fw_addr_offset : + 0; + __entry->job = job; + __entry->job_type = job->type; + __entry->sync_op_count = sync_op_count;), + TP_printk("pvr_dev=%p ctx=%p fw_obj=%p fw_addr=0x%x hwrt_addr=0x%x job=%p job_type=%s sync_op_count=%u", + __entry->pvr_dev, + __entry->ctx, + __entry->fw_obj, + __entry->fw_addr, + __entry->hwrt_addr, + __entry->job, + PVR_JOB_TYPE_TO_STR(__entry->job_type), + __entry->sync_op_count) +); + +#undef PVR_JOB_TYPE_TO_STR + +TRACE_EVENT(pvr_job_submit_fw, + TP_PROTO(struct pvr_job *job), + TP_ARGS(job), + TP_STRUCT__entry(__field(struct pvr_job *, job) + __field(u32, done_seqno)), + TP_fast_assign(__entry->job = job; + __entry->done_seqno = job->done_fence->seqno;), + TP_printk("job=%p done_seqno=%u", + __entry->job, + __entry->done_seqno) +); + +TRACE_EVENT(pvr_job_done, + TP_PROTO(struct pvr_job *job), + TP_ARGS(job), + TP_STRUCT__entry(__field(struct pvr_job *, job)), + TP_fast_assign(__entry->job = job;), + TP_printk("job=%p", __entry->job) +); + +#endif /* PVR_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/imagination +#define TRACE_INCLUDE_FILE pvr_trace +#include diff --git a/drivers/gpu/drm/imagination/pvr_trace_points.c b/drivers/gpu/drm/imagination/pvr_trace_points.c new file mode 100644 index 000000000000..71b53f267af0 --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_trace_points.c @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright (c) 2026 Imagination Technologies Ltd. */ + +#ifndef __CHECKER__ +#define CREATE_TRACE_POINTS +#include "pvr_trace.h" +#endif From 88658ff0e4e7f46dbf8179af1280f2cb295fb0cb Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 19 May 2026 15:12:05 +0200 Subject: [PATCH 24/38] drm/imagination: Fix missing argument in pvr_power_fw_{en,dis}able() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 42577ba79fbf ("drm/imagination: Rename FW booted to FW initialised") dropped by mistake the last argument of the functions pvr_power_fw_enable() and pvr_power_fw_disable(), leading to the following compile error: CC [M] drivers/gpu/drm/imagination/pvr_power.o drivers/gpu/drm/imagination/pvr_power.c: In function ‘pvr_power_device_suspend’: drivers/gpu/drm/imagination/pvr_power.c:382:23: error: too few arguments to function ‘pvr_power_fw_disable’; expected 3, have 2 382 | err = pvr_power_fw_disable(pvr_dev, false); | ^~~~~~~~~~~~~~~~~~~~ drivers/gpu/drm/imagination/pvr_power.c:93:1: note: declared here 93 | pvr_power_fw_disable(struct pvr_device *pvr_dev, bool hard_reset, bool rpm_suspend) | ^~~~~~~~~~~~~~~~~~~~ drivers/gpu/drm/imagination/pvr_power.c: In function ‘pvr_power_device_resume’: drivers/gpu/drm/imagination/pvr_power.c:412:23: error: too few arguments to function ‘pvr_power_fw_enable’; expected 2, have 1 412 | err = pvr_power_fw_enable(pvr_dev); | ^~~~~~~~~~~~~~~~~~~ drivers/gpu/drm/imagination/pvr_power.c:122:1: note: declared here 122 | pvr_power_fw_enable(struct pvr_device *pvr_dev, bool rpm_resume) | ^~~~~~~~~~~~~~~~~~~ make[6]: *** [scripts/Makefile.build:289: drivers/gpu/drm/imagination/pvr_power.o] Error 1 make[5]: *** [scripts/Makefile.build:548: drivers/gpu/drm/imagination] Error 2 make[4]: *** [scripts/Makefile.build:548: drivers/gpu/drm] Error 2 make[3]: *** [scripts/Makefile.build:548: drivers/gpu] Error 2 make[2]: *** [scripts/Makefile.build:548: drivers] Error 2 make[1]: *** [/home/javier/devel/linux/Makefile:2141: .] Error 2 make: *** [Makefile:248: __sub-make] Error 2 Fixes: 42577ba79fbf ("drm/imagination: Rename FW booted to FW initialised") Reviewed-by: Brajesh Gupta Link: https://patch.msgid.link/20260519131239.1291732-1-javierm@redhat.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/imagination/pvr_power.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c index 0ed9e7be604b..a71d5b35601e 100644 --- a/drivers/gpu/drm/imagination/pvr_power.c +++ b/drivers/gpu/drm/imagination/pvr_power.c @@ -379,7 +379,7 @@ pvr_power_device_suspend(struct device *dev) return -EIO; if (READ_ONCE(pvr_dev->fw_dev.initialised)) { - err = pvr_power_fw_disable(pvr_dev, false); + err = pvr_power_fw_disable(pvr_dev, false, true); if (err) goto err_drm_dev_exit; } @@ -409,7 +409,7 @@ pvr_power_device_resume(struct device *dev) goto err_drm_dev_exit; if (READ_ONCE(pvr_dev->fw_dev.initialised)) { - err = pvr_power_fw_enable(pvr_dev); + err = pvr_power_fw_enable(pvr_dev, true); if (err) goto err_power_off; } From 7b5121c3374e24c8f6490b54f347eb06ee16028c Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Tue, 28 Apr 2026 21:44:48 +0200 Subject: [PATCH 25/38] drm/virtio: support VIRTIO_GPU_F_BLOB_ALIGNMENT Support VIRTIO_GPU_F_BLOB_ALIGNMENT, a feature that indicates the device provides a valid blob_alignment field in its configuration, and that both RESOURCE_CREATE_BLOB and RESOURCE_MAP_BLOB requests must be aligned to that value. Signed-off-by: Sergio Lopez Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260428194450.518296-2-slp@redhat.com --- drivers/gpu/drm/virtio/virtgpu_drv.c | 1 + drivers/gpu/drm/virtio/virtgpu_drv.h | 2 ++ drivers/gpu/drm/virtio/virtgpu_kms.c | 14 +++++++++++--- include/uapi/linux/virtio_gpu.h | 9 +++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.c b/drivers/gpu/drm/virtio/virtgpu_drv.c index a5ce96fb8a1d..812ee3f5e4aa 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.c +++ b/drivers/gpu/drm/virtio/virtgpu_drv.c @@ -163,6 +163,7 @@ static unsigned int features[] = { VIRTIO_GPU_F_RESOURCE_UUID, VIRTIO_GPU_F_RESOURCE_BLOB, VIRTIO_GPU_F_CONTEXT_INIT, + VIRTIO_GPU_F_BLOB_ALIGNMENT, }; static struct virtio_driver virtio_gpu_driver = { .feature_table = features, diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 6f49213e23f8..04fe15d877cd 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -258,6 +258,7 @@ struct virtio_gpu_device { bool has_resource_blob; bool has_host_visible; bool has_context_init; + bool has_blob_alignment; struct virtio_shm_region host_visible_region; struct drm_mm host_visible_mm; @@ -271,6 +272,7 @@ struct virtio_gpu_device { uint32_t num_capsets; uint64_t capset_id_mask; struct list_head cap_cache; + uint32_t blob_alignment; /* protects uuid state when exporting */ spinlock_t resource_export_lock; diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c index 80ba69b4860b..cfde9f573df6 100644 --- a/drivers/gpu/drm/virtio/virtgpu_kms.c +++ b/drivers/gpu/drm/virtio/virtgpu_kms.c @@ -124,7 +124,7 @@ int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev) struct virtio_gpu_device *vgdev; /* this will expand later */ struct virtqueue *vqs[2]; - u32 num_scanouts, num_capsets; + u32 num_scanouts, num_capsets, blob_alignment; int ret = 0; if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) @@ -198,14 +198,22 @@ int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev) if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_CONTEXT_INIT)) vgdev->has_context_init = true; + if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_BLOB_ALIGNMENT)) { + vgdev->has_blob_alignment = true; + virtio_cread_le(vgdev->vdev, struct virtio_gpu_config, + blob_alignment, &blob_alignment); + vgdev->blob_alignment = blob_alignment; + } + DRM_INFO("features: %cvirgl %cedid %cresource_blob %chost_visible", vgdev->has_virgl_3d ? '+' : '-', vgdev->has_edid ? '+' : '-', vgdev->has_resource_blob ? '+' : '-', vgdev->has_host_visible ? '+' : '-'); - DRM_INFO("features: %ccontext_init\n", - vgdev->has_context_init ? '+' : '-'); + DRM_INFO("features: %ccontext_init %cblob_alignment\n", + vgdev->has_context_init ? '+' : '-', + vgdev->has_blob_alignment ? '+' : '-'); ret = virtio_find_vqs(vgdev->vdev, 2, vqs, vqs_info, NULL); if (ret) { diff --git a/include/uapi/linux/virtio_gpu.h b/include/uapi/linux/virtio_gpu.h index be109777d10d..4f530d90058c 100644 --- a/include/uapi/linux/virtio_gpu.h +++ b/include/uapi/linux/virtio_gpu.h @@ -64,6 +64,14 @@ * context_init and multiple timelines */ #define VIRTIO_GPU_F_CONTEXT_INIT 4 +/* + * The device provides a valid blob_alignment + * field in its configuration and both + * VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB and + * VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB requests + * must be aligned to that value. + */ +#define VIRTIO_GPU_F_BLOB_ALIGNMENT 5 enum virtio_gpu_ctrl_type { VIRTIO_GPU_UNDEFINED = 0, @@ -365,6 +373,7 @@ struct virtio_gpu_config { __le32 events_clear; __le32 num_scanouts; __le32 num_capsets; + __le32 blob_alignment; }; /* simple formats for fbcon/X use */ From 47248e0d8264452762bd47a8b3c89665b8235e93 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Tue, 28 Apr 2026 21:44:49 +0200 Subject: [PATCH 26/38] drm/virtio: honor blob_alignment requirements If VIRTIO_GPU_F_BLOB_ALIGNMENT has been negotiated, blob size must be aligned to blob_alignment. Validate this in verify_blob() so that invalid requests are rejected early. Signed-off-by: Sergio Lopez Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260428194450.518296-3-slp@redhat.com --- drivers/gpu/drm/virtio/virtgpu_ioctl.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c index 01daa72b1310..85e2fafe4152 100644 --- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c +++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c @@ -490,6 +490,11 @@ static int verify_blob(struct virtio_gpu_device *vgdev, params->blob = true; params->blob_flags = rc_blob->blob_flags; params->blob_hints = rc_blob->blob_hints; + + if (vgdev->has_blob_alignment && + !IS_ALIGNED(params->size, vgdev->blob_alignment)) + return -EINVAL; + return 0; } From 6bd7e82e26531541a6023f060ba749547b9868ac Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Tue, 28 Apr 2026 21:44:50 +0200 Subject: [PATCH 27/38] drm/virtio: add VIRTGPU_PARAM_BLOB_ALIGNMENT to params Add VIRTGPU_PARAM_BLOB_ALIGNMENT as a param that can be read with VIRTGPU_GETPARAM by userspace applications running in the guest to obtain the host's page size and find out the right alignment to be used in shared memory allocations. Signed-off-by: Sergio Lopez Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260428194450.518296-4-slp@redhat.com --- drivers/gpu/drm/virtio/virtgpu_ioctl.c | 5 +++++ include/uapi/drm/virtgpu_drm.h | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c index 85e2fafe4152..3d8e4ccdb7c1 100644 --- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c +++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c @@ -117,6 +117,11 @@ static int virtio_gpu_getparam_ioctl(struct drm_device *dev, void *data, case VIRTGPU_PARAM_EXPLICIT_DEBUG_NAME: value = vgdev->has_context_init ? 1 : 0; break; + case VIRTGPU_PARAM_BLOB_ALIGNMENT: + if (!vgdev->has_blob_alignment) + return -ENOENT; + value = vgdev->blob_alignment; + break; default: return -EINVAL; } diff --git a/include/uapi/drm/virtgpu_drm.h b/include/uapi/drm/virtgpu_drm.h index ba09a4ee3e77..95587e12aed5 100644 --- a/include/uapi/drm/virtgpu_drm.h +++ b/include/uapi/drm/virtgpu_drm.h @@ -98,6 +98,7 @@ struct drm_virtgpu_execbuffer { #define VIRTGPU_PARAM_CONTEXT_INIT 6 /* DRM_VIRTGPU_CONTEXT_INIT */ #define VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs 7 /* Bitmask of supported capability set ids */ #define VIRTGPU_PARAM_EXPLICIT_DEBUG_NAME 8 /* Ability to set debug name from userspace */ +#define VIRTGPU_PARAM_BLOB_ALIGNMENT 9 /* Device alignment requirements for blobs */ struct drm_virtgpu_getparam { __u64 param; From a48bbcc7ac739e93562d6148c6fa504c2e9f22f8 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Tue, 19 May 2026 13:52:47 +0530 Subject: [PATCH 28/38] drm/virtio: use uninterruptible resv lock for plane updates virtio_gpu_cursor_plane_update() and virtio_gpu_resource_flush() lock the framebuffer BO's dma_resv via virtio_gpu_array_lock_resv() and ignore its return value. The function can fail with -EINTR from dma_resv_lock_interruptible() (signal during lock wait) or with -ENOMEM from dma_resv_reserve_fences() (fence slot allocation), leaving the resv lock not held. The queue path then walks the object array and calls dma_resv_add_fence(), which requires the lock held; with lockdep enabled this trips dma_resv_assert_held(): WARNING: drivers/dma-buf/dma-resv.c:296 at dma_resv_add_fence+0x71e/0x840 Call Trace: virtio_gpu_array_add_fence virtio_gpu_queue_ctrl_sgs virtio_gpu_queue_fenced_ctrl_buffer virtio_gpu_cursor_plane_update drm_atomic_helper_commit_planes drm_atomic_helper_commit_tail commit_tail drm_atomic_helper_commit drm_atomic_commit drm_atomic_helper_update_plane __setplane_atomic drm_mode_cursor_universal drm_mode_cursor_common drm_mode_cursor_ioctl drm_ioctl __x64_sys_ioctl Beyond the WARN, mutating the dma_resv fence list without the lock races with concurrent readers/writers and can corrupt the list. Both call sites run inside the .atomic_update plane callback, which DRM atomic helpers do not allow to fail (by the time it runs, the commit has been signed off to userspace and there is no clean rollback path). Moving the lock acquisition to .prepare_fb was rejected because the broader lock scope deadlocks against other BO locking paths in the same atomic commit. Introduce virtio_gpu_lock_one_resv_uninterruptible() that uses dma_resv_lock() instead of dma_resv_lock_interruptible(). This eliminates the -EINTR failure mode -- the realistic syzbot trigger -- without extending the lock hold across the commit. The helper locks a single BO and rejects nents > 1 with -EINVAL; both fix sites lock exactly one BO. Use it from virtio_gpu_cursor_plane_update() and virtio_gpu_resource_flush(); check the return value to handle the remaining -ENOMEM case from dma_resv_reserve_fences() by freeing the objs and skipping the plane update for that frame. The framebuffer BOs touched here are not shared with other contexts and lock contention is expected to be brief, so the loss of signal-interruptibility is acceptable. Other callers of virtio_gpu_array_lock_resv() (the ioctl paths) continue to use the interruptible variant. The bug was reported by syzbot, triggered via fault injection (fail_nth) on the DRM_IOCTL_MODE_CURSOR path, which forces the -ENOMEM branch in dma_resv_reserve_fences(). Reported-by: syzbot+72bd3dd3a5d5f39a0271@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=72bd3dd3a5d5f39a0271 Fixes: 5cfd31c5b3a3 ("drm/virtio: fix virtio_gpu_cursor_plane_update().") Cc: stable@vger.kernel.org Signed-off-by: Deepanshu Kartikey Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260519082247.34470-1-kartikey406@gmail.com --- drivers/gpu/drm/virtio/virtgpu_drv.h | 1 + drivers/gpu/drm/virtio/virtgpu_gem.c | 17 +++++++++++++++++ drivers/gpu/drm/virtio/virtgpu_plane.c | 10 ++++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 04fe15d877cd..7449907754a4 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -320,6 +320,7 @@ virtio_gpu_array_from_handles(struct drm_file *drm_file, u32 *handles, u32 nents void virtio_gpu_array_add_obj(struct virtio_gpu_object_array *objs, struct drm_gem_object *obj); int virtio_gpu_array_lock_resv(struct virtio_gpu_object_array *objs); +int virtio_gpu_lock_one_resv_uninterruptible(struct virtio_gpu_object_array *objs); void virtio_gpu_array_unlock_resv(struct virtio_gpu_object_array *objs); void virtio_gpu_array_add_fence(struct virtio_gpu_object_array *objs, struct dma_fence *fence); diff --git a/drivers/gpu/drm/virtio/virtgpu_gem.c b/drivers/gpu/drm/virtio/virtgpu_gem.c index f22dc5c21cd4..435d37d36034 100644 --- a/drivers/gpu/drm/virtio/virtgpu_gem.c +++ b/drivers/gpu/drm/virtio/virtgpu_gem.c @@ -238,6 +238,23 @@ int virtio_gpu_array_lock_resv(struct virtio_gpu_object_array *objs) return ret; } +int virtio_gpu_lock_one_resv_uninterruptible(struct virtio_gpu_object_array *objs) +{ + int ret; + + if (objs->nents != 1) + return -EINVAL; + + dma_resv_lock(objs->objs[0]->resv, NULL); + + ret = dma_resv_reserve_fences(objs->objs[0]->resv, 1); + if (ret) { + virtio_gpu_array_unlock_resv(objs); + return ret; + } + return 0; +} + void virtio_gpu_array_unlock_resv(struct virtio_gpu_object_array *objs) { if (objs->nents == 1) { diff --git a/drivers/gpu/drm/virtio/virtgpu_plane.c b/drivers/gpu/drm/virtio/virtgpu_plane.c index 73aa70bd4231..1d1b27ece62a 100644 --- a/drivers/gpu/drm/virtio/virtgpu_plane.c +++ b/drivers/gpu/drm/virtio/virtgpu_plane.c @@ -215,7 +215,10 @@ static void virtio_gpu_resource_flush(struct drm_plane *plane, if (!objs) return; virtio_gpu_array_add_obj(objs, vgfb->base.obj[0]); - virtio_gpu_array_lock_resv(objs); + if (virtio_gpu_lock_one_resv_uninterruptible(objs)) { + virtio_gpu_array_put_free(objs); + return; + } virtio_gpu_cmd_resource_flush(vgdev, bo->hw_res_handle, x, y, width, height, objs, vgplane_st->fence); @@ -459,7 +462,10 @@ static void virtio_gpu_cursor_plane_update(struct drm_plane *plane, if (!objs) return; virtio_gpu_array_add_obj(objs, vgfb->base.obj[0]); - virtio_gpu_array_lock_resv(objs); + if (virtio_gpu_lock_one_resv_uninterruptible(objs)) { + virtio_gpu_array_put_free(objs); + return; + } virtio_gpu_cmd_transfer_to_host_2d (vgdev, 0, plane->state->crtc_w, From 9392e7340bffb406a705de755adfb44eab547d40 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 5 May 2026 13:05:07 +0300 Subject: [PATCH 29/38] drm/rockchip: dw_hdmi: avoid direct dereference of phy->dev.of_node The dw_hdmi-rockchip driver validates pixel clock rates against the HDMI PHY's internal clock provider on certain SoCs like RK3328. This is currently achieved by dereferencing hdmi->phy->dev.of_node to obtain the provider node, which violates the Generic PHY API's encapsulation (the goal is for struct phy to be an opaque pointer with a hidden definition, to be interacted with only using API functions or NULL pointer checks, for the case where optional variants of phy_get() did not find a PHY). Refactor dw_hdmi_rockchip_bind() to perform a manual phandle lookup on the "hdmi" PHY index within the controller's DT node. This provides a parallel path to the clock provider's OF node without relying on the internal structure of the struct phy handle. Signed-off-by: Vladimir Oltean Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260505100523.1922388-16-vladimir.oltean@nxp.com --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 25 ++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 0dc1eb5d2ae3..7abb42e486c0 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -537,21 +537,22 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); + struct device_node *np = dev_of_node(dev); struct dw_hdmi_plat_data *plat_data; const struct of_device_id *match; struct drm_device *drm = data; struct drm_encoder *encoder; struct rockchip_hdmi *hdmi; - int ret; + int ret, index; - if (!pdev->dev.of_node) + if (!np) return -ENODEV; hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); if (!hdmi) return -ENOMEM; - match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node); + match = of_match_node(dw_hdmi_rockchip_dt_ids, np); plat_data = devm_kmemdup(&pdev->dev, match->data, sizeof(*plat_data), GFP_KERNEL); if (!plat_data) @@ -564,9 +565,9 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, plat_data->priv_data = hdmi; encoder = &hdmi->encoder.encoder; - encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, np); rockchip_drm_encoder_set_crtc_endpoint_id(&hdmi->encoder, - dev->of_node, 0, 0); + np, 0, 0); /* * If we failed to find the CRTC(s) which this encoder is @@ -588,13 +589,17 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, return dev_err_probe(hdmi->dev, ret, "failed to get phy\n"); } - if (hdmi->phy) { + index = of_property_match_string(np, "phy-names", "hdmi"); + if (index >= 0) { struct of_phandle_args clkspec; - clkspec.np = hdmi->phy->dev.of_node; - hdmi->hdmiphy_clk = of_clk_get_from_provider(&clkspec); - if (IS_ERR(hdmi->hdmiphy_clk)) - hdmi->hdmiphy_clk = NULL; + if (!of_parse_phandle_with_args(np, "phys", "#phy-cells", index, + &clkspec)) { + hdmi->hdmiphy_clk = of_clk_get_from_provider(&clkspec); + of_node_put(clkspec.np); + if (IS_ERR(hdmi->hdmiphy_clk)) + hdmi->hdmiphy_clk = NULL; + } } if (hdmi->chip_data == &rk3568_chip_data) { From 988369959e9cc6dad1c3260ebaeb7a3bb5653ab2 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 20 May 2026 16:43:37 +0200 Subject: [PATCH 30/38] video/hdmi: Add common TMDS character rate constants Several DRM drivers already define their own constants for minimum and maximum TMDS character rates. By defining common rate constants in a shared header, drivers can just use them instead of having driver local define macros or use magic numbers. The values defined in the header correspond to maximum TMDS character rates defined by each HDMI specification version: - HDMI_TMDS_CHAR_RATE_MIN_HZ: 25 MHz (minimum for all versions) - HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ: 165 MHz (HDMI 1.0 maximum) - HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ: 340 MHz (HDMI 1.3 maximum) - HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ: 600 MHz (HDMI 2.0 maximum) Suggested-by: Maxime Ripard Reviewed-by: Heiko Stuebner Reviewed-by: Neil Armstrong Reviewed-by: Dmitry Baryshkov Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260520144424.1633354-2-javierm@redhat.com Signed-off-by: Javier Martinez Canillas --- include/linux/hdmi.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h index 96bda41d9148..8dab78e1f61b 100644 --- a/include/linux/hdmi.h +++ b/include/linux/hdmi.h @@ -50,6 +50,12 @@ enum hdmi_infoframe_type { HDMI_INFOFRAME_TYPE_DRM = 0x87, }; +/* HDMI spec maximum TMDS character rates, in Hz */ +#define HDMI_TMDS_CHAR_RATE_MIN_HZ 25000000 +#define HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ 165000000 +#define HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ 340000000 +#define HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ 600000000 + #define HDMI_IEEE_OUI 0x000c03 #define HDMI_FORUM_IEEE_OUI 0xc45dd8 #define HDMI_INFOFRAME_HEADER_SIZE 4 From dad915b6d8bcfacd942b3d1af364224748b6e943 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 20 May 2026 16:43:38 +0200 Subject: [PATCH 31/38] drm/bridge: dw-hdmi: Use the common TMDS char rate constant Replace the driver local HDMI14_MAX_TMDSCLK define with the shared constant defined in the header. The local define incorrectly referenced HDMI 1.4, but the 340 MHz maximum TMDS character rate was actually introduced in HDMI 1.3. Suggested-by: Maxime Ripard Reviewed-by: Heiko Stuebner Reviewed-by: Neil Armstrong Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260520144424.1633354-3-javierm@redhat.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 41b3a9cfa2f5..17d5caedb32e 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -51,8 +51,6 @@ /* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */ #define SCDC_MIN_SOURCE_VERSION 0x1 -#define HDMI14_MAX_TMDSCLK 340000000 - static const u16 csc_coeff_default[3][4] = { { 0x2000, 0x0000, 0x0000, 0x0000 }, { 0x0000, 0x2000, 0x0000, 0x0000 }, @@ -1426,7 +1424,7 @@ void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi, /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */ if (dw_hdmi_support_scdc(hdmi, display)) { - if (mtmdsclock > HDMI14_MAX_TMDSCLK) + if (mtmdsclock > HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ) drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, 1); else drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, 0); @@ -1671,7 +1669,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, } /* Wait for resuming transmission of TMDS clock and data */ - if (mtmdsclock > HDMI14_MAX_TMDSCLK) + if (mtmdsclock > HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ) msleep(100); return dw_hdmi_phy_power_on(hdmi); @@ -2032,7 +2030,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, /* Set up HDMI_FC_INVIDCONF */ inv_val = (hdmi->hdmi_data.hdcp_enable || (dw_hdmi_support_scdc(hdmi, display) && - (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK || + (vmode->mtmdsclock > HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ || hdmi_info->scdc.scrambling.low_rates)) ? HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE : HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE); @@ -2100,7 +2098,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, /* Scrambling Control */ if (dw_hdmi_support_scdc(hdmi, display)) { - if (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK || + if (vmode->mtmdsclock > HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ || hdmi_info->scdc.scrambling.low_rates) { /* * HDMI2.0 Specifies the following procedure: From fb145be7964d4a602e5716e465cf9d9b4107b516 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 20 May 2026 16:43:39 +0200 Subject: [PATCH 32/38] drm/bridge: dw-hdmi-qp: Use the common TMDS char rate constant Replace the driver local HDMI14_MAX_TMDSCLK define with the shared constant defined in the header. The local define incorrectly referenced HDMI 1.4, but the 340 MHz maximum TMDS character rate was actually introduced in HDMI 1.3. Suggested-by: Maxime Ripard Reviewed-by: Neil Armstrong Reviewed-by: Heiko Stuebner Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260520144424.1633354-4-javierm@redhat.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 0dbb12743609..1c214a8e6dc2 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -38,8 +38,6 @@ #define DDC_CI_ADDR 0x37 #define DDC_SEGMENT_ADDR 0x30 -#define HDMI14_MAX_TMDSCLK 340000000 - #define SCRAMB_POLL_DELAY_MS 3000 /* @@ -835,9 +833,9 @@ dw_hdmi_qp_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge, /* * TODO: when hdmi->no_hpd is 1 we must not support modes that * require scrambling, including every mode with a clock above - * HDMI14_MAX_TMDSCLK. + * HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ. */ - if (rate > HDMI14_MAX_TMDSCLK) { + if (rate > HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ) { dev_dbg(hdmi->dev, "Unsupported TMDS char rate: %lld\n", rate); return MODE_CLOCK_HIGH; } From 0d321c4ad9405c236ddae2ce4ab4d58c50ebdb74 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 20 May 2026 16:43:40 +0200 Subject: [PATCH 33/38] drm/bridge: inno-hdmi: Use the common TMDS char rate constant Replace the driver local INNO_HDMI_MIN_TMDS_CLOCK define with the shared constant defined in the header. Suggested-by: Maxime Ripard Reviewed-by: Heiko Stuebner Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260520144424.1633354-5-javierm@redhat.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/bridge/inno-hdmi.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/inno-hdmi.c b/drivers/gpu/drm/bridge/inno-hdmi.c index 1091af29ad8f..5fa533a4eb34 100644 --- a/drivers/gpu/drm/bridge/inno-hdmi.c +++ b/drivers/gpu/drm/bridge/inno-hdmi.c @@ -31,8 +31,6 @@ #include #include -#define INNO_HDMI_MIN_TMDS_CLOCK 25000000U - #define DDC_SEGMENT_ADDR 0x30 #define HDMI_SCL_RATE (100 * 1000) @@ -820,7 +818,7 @@ static enum drm_mode_status inno_hdmi_bridge_mode_valid(struct drm_bridge *bridg mpixelclk = mode->clock * 1000; - if (mpixelclk < INNO_HDMI_MIN_TMDS_CLOCK) + if (mpixelclk < HDMI_TMDS_CHAR_RATE_MIN_HZ) return MODE_CLOCK_LOW; if (inno_hdmi_find_phy_config(hdmi, mpixelclk) < 0) From b08582317ee24161a376359d81c096cd2dd8af50 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 20 May 2026 16:43:41 +0200 Subject: [PATCH 34/38] drm/sti: hdmi: Use the common TMDS char rate constants Replace the 340000000 and 165000000 magic numbers with the shared constants defined in the header. Suggested-by: Maxime Ripard Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260520144424.1633354-6-javierm@redhat.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c index d25ecd4f4b67..4fe0c54a096f 100644 --- a/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c +++ b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c @@ -4,6 +4,8 @@ * Author: Vincent Abriou for STMicroelectronics. */ +#include + #include #include "sti_hdmi_tx3g4c28phy.h" @@ -102,7 +104,7 @@ static bool sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi) tmdsck = ckpxpll; pllctrl |= 40 << PLL_CFG_NDIV_SHIFT; - if (tmdsck > 340000000) { + if (tmdsck > HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ) { DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck); goto err; } @@ -135,7 +137,7 @@ static bool sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi) HDMI_SRZ_CFG_EN_BIASRES_DETECTION | HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION); - if (tmdsck > 165000000) + if (tmdsck > HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ) val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION; /* From 3148266e5a0a26ddc5b62d10e534571df0603111 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 20 May 2026 16:43:42 +0200 Subject: [PATCH 35/38] drm/sun4i: hdmi: Use the common TMDS char rate constant Replace the 165000000 magic number with the shared constant defined in the header. The old comment referenced "HDMI <= 1.2" but 165 MHz is actually the maximum TMDS character rate defined by the HDMI 1.0 spec. Suggested-by: Maxime Ripard Reviewed-by: Chen-Yu Tsai Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260520144424.1633354-7-javierm@redhat.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index 07e2afcb4f95..74c7c3720ba8 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -189,8 +189,8 @@ sun4i_hdmi_connector_clock_valid(const struct drm_connector *connector, if (mode->flags & DRM_MODE_FLAG_DBLCLK) return MODE_BAD; - /* 165 MHz is the typical max pixelclock frequency for HDMI <= 1.2 */ - if (clock > 165000000) + /* HDMI 1.0 max TMDS character rate */ + if (clock > HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ) return MODE_CLOCK_HIGH; rounded_rate = clk_round_rate(hdmi->tmds_clk, clock); From 89ccc9aaa298ed273d6152c41e1160d5ce356627 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 20 May 2026 16:43:43 +0200 Subject: [PATCH 36/38] drm/msm/hdmi: Use the common TMDS char rate constants in 8996 PHY Replace the driver local defines with the shared constants defined in the header for the minimum and maximum TMDS character rates. Suggested-by: Maxime Ripard Reviewed-by: Dmitry Baryshkov Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260520144424.1633354-8-javierm@redhat.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/msm/hdmi/hdmi_phy_8996.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_phy_8996.c b/drivers/gpu/drm/msm/hdmi/hdmi_phy_8996.c index 36e928b0fd5a..71da20322b3d 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_phy_8996.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_phy_8996.c @@ -11,9 +11,6 @@ #define HDMI_VCO_MAX_FREQ 12000000000UL #define HDMI_VCO_MIN_FREQ 8000000000UL -#define HDMI_PCLK_MAX_FREQ 600000000 -#define HDMI_PCLK_MIN_FREQ 25000000 - #define HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD 3400000000UL #define HDMI_DIG_FREQ_BIT_CLK_THRESHOLD 1500000000UL #define HDMI_MID_FREQ_BIT_CLK_THRESHOLD 750000000UL @@ -632,7 +629,8 @@ static int hdmi_8996_pll_prepare(struct clk_hw *hw) static int hdmi_8996_pll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { - req->rate = clamp_t(unsigned long, req->rate, HDMI_PCLK_MIN_FREQ, HDMI_PCLK_MAX_FREQ); + req->rate = clamp_t(unsigned long, req->rate, HDMI_TMDS_CHAR_RATE_MIN_HZ, + HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ); return 0; } From a36423a9008d83dfeadc6de509d2cfb5d2e4325b Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 20 May 2026 16:43:44 +0200 Subject: [PATCH 37/38] drm/msm/hdmi: Use the common TMDS char rate constants in 8998 PHY Replace the driver local defines with the shared constants defined in the header for the minimum and maximum TMDS character rates. Suggested-by: Maxime Ripard Reviewed-by: Dmitry Baryshkov Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260520144424.1633354-9-javierm@redhat.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/msm/hdmi/hdmi_phy_8998.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_phy_8998.c b/drivers/gpu/drm/msm/hdmi/hdmi_phy_8998.c index a86ff3706369..05c3ffad858d 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_phy_8998.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_phy_8998.c @@ -12,9 +12,6 @@ #define HDMI_VCO_MAX_FREQ 12000000000UL #define HDMI_VCO_MIN_FREQ 8000000000UL -#define HDMI_PCLK_MAX_FREQ 600000000 -#define HDMI_PCLK_MIN_FREQ 25000000 - #define HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD 3400000000UL #define HDMI_DIG_FREQ_BIT_CLK_THRESHOLD 1500000000UL #define HDMI_MID_FREQ_BIT_CLK_THRESHOLD 750000000UL @@ -649,7 +646,8 @@ static int hdmi_8998_pll_prepare(struct clk_hw *hw) static int hdmi_8998_pll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { - req->rate = clamp_t(unsigned long, req->rate, HDMI_PCLK_MIN_FREQ, HDMI_PCLK_MAX_FREQ); + req->rate = clamp_t(unsigned long, req->rate, HDMI_TMDS_CHAR_RATE_MIN_HZ, + HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ); return 0; } From bfb76105b864af5553263a2156eecd7f08d7810b Mon Sep 17 00:00:00 2001 From: Sen Wang Date: Wed, 18 Mar 2026 10:46:36 -0500 Subject: [PATCH 38/38] drm/bridge: ite-it66121: Convert to DRM HDMI Audio Helper Convert the IT66121 HDMI bridge driver from manually registering an hdmi-codec platform device to using the DRM HDMI Audio Helper framework via DRM_BRIDGE_OP_HDMI_AUDIO instead. The previous implementation manually allocated hdmi_codec_pdata, registered the platform device, and implemented hdmi_codec_ops callbacks including get_eld. The new approach sets DRM_BRIDGE_OP_HDMI_AUDIO on the bridge, letting the framework handle the codec registration. This also resolves some non-compliance issues with the current audio implementation, such as HDMI audio advertising a non-functional capture stream to userspace. The audio callbacks are converted from hdmi_codec_ops signatures to drm_bridge_funcs hdmi_audio callbacks: - it66121_audio_hw_params -> it66121_hdmi_audio_prepare - it66121_audio_startup -> it66121_hdmi_audio_startup - it66121_audio_shutdown -> it66121_hdmi_audio_shutdown - it66121_audio_mute -> it66121_hdmi_audio_mute_stream The it66121_audio_get_eld, it66121_audio_codec_ops, and it66121_audio_codec_init functions are removed as the framework handles these responsibilities. Suggested-by: Dmitry Baryshkov Signed-off-by: Sen Wang Reviewed-by: Javier Martinez Canillas Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260318154636.3230454-1-sen@ti.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/bridge/ite-it66121.c | 138 ++++++++++----------------- 1 file changed, 48 insertions(+), 90 deletions(-) diff --git a/drivers/gpu/drm/bridge/ite-it66121.c b/drivers/gpu/drm/bridge/ite-it66121.c index 19a027d75b61..19e188fe6e3b 100644 --- a/drivers/gpu/drm/bridge/ite-it66121.c +++ b/drivers/gpu/drm/bridge/ite-it66121.c @@ -306,7 +306,6 @@ struct it66121_ctx { struct mutex lock; /* Protects fields below and device registers */ struct hdmi_avi_infoframe hdmi_avi_infoframe; struct { - struct platform_device *pdev; u8 ch_enable; u8 fs; u8 swl; @@ -902,24 +901,6 @@ static const struct drm_edid *it66121_bridge_edid_read(struct drm_bridge *bridge return drm_edid; } -static const struct drm_bridge_funcs it66121_bridge_funcs = { - .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, - .atomic_reset = drm_atomic_helper_bridge_reset, - .attach = it66121_bridge_attach, - .atomic_get_output_bus_fmts = it66121_bridge_atomic_get_output_bus_fmts, - .atomic_get_input_bus_fmts = it66121_bridge_atomic_get_input_bus_fmts, - .atomic_enable = it66121_bridge_enable, - .atomic_disable = it66121_bridge_disable, - .atomic_check = it66121_bridge_check, - .mode_set = it66121_bridge_mode_set, - .mode_valid = it66121_bridge_mode_valid, - .detect = it66121_bridge_detect, - .edid_read = it66121_bridge_edid_read, - .hpd_enable = it66121_bridge_hpd_enable, - .hpd_disable = it66121_bridge_hpd_disable, -}; - static irqreturn_t it66121_irq_threaded_handler(int irq, void *dev_id) { int ret; @@ -1225,14 +1206,16 @@ static int it661221_audio_ch_enable(struct it66121_ctx *ctx, bool enable) return ret; } -static int it66121_audio_hw_params(struct device *dev, void *data, - struct hdmi_codec_daifmt *daifmt, - struct hdmi_codec_params *params) +static int it66121_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) { u8 fs; u8 swl; int ret; - struct it66121_ctx *ctx = dev_get_drvdata(dev); + struct it66121_ctx *ctx = container_of(bridge, struct it66121_ctx, bridge); + struct device *dev = ctx->dev; static u8 iec60958_chstat[5]; unsigned int channels = params->channels; unsigned int sample_rate = params->sample_rate; @@ -1379,41 +1362,44 @@ static int it66121_audio_hw_params(struct device *dev, void *data, return ret; } -static int it66121_audio_startup(struct device *dev, void *data) +static int it66121_hdmi_audio_startup(struct drm_bridge *bridge, + struct drm_connector *connector) { int ret; - struct it66121_ctx *ctx = dev_get_drvdata(dev); + struct it66121_ctx *ctx = container_of(bridge, struct it66121_ctx, bridge); mutex_lock(&ctx->lock); ret = it661221_audio_output_enable(ctx, true); if (ret) - dev_err(dev, "Failed to enable audio output: %d\n", ret); + dev_err(ctx->dev, "Failed to enable audio output: %d\n", ret); mutex_unlock(&ctx->lock); return ret; } -static void it66121_audio_shutdown(struct device *dev, void *data) +static void it66121_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) { int ret; - struct it66121_ctx *ctx = dev_get_drvdata(dev); + struct it66121_ctx *ctx = container_of(bridge, struct it66121_ctx, bridge); mutex_lock(&ctx->lock); ret = it661221_audio_output_enable(ctx, false); if (ret) - dev_err(dev, "Failed to disable audio output: %d\n", ret); + dev_err(ctx->dev, "Failed to disable audio output: %d\n", ret); mutex_unlock(&ctx->lock); } -static int it66121_audio_mute(struct device *dev, void *data, - bool enable, int direction) +static int it66121_hdmi_audio_mute_stream(struct drm_bridge *bridge, + struct drm_connector *connector, + bool enable, int direction) { int ret; - struct it66121_ctx *ctx = dev_get_drvdata(dev); + struct it66121_ctx *ctx = container_of(bridge, struct it66121_ctx, bridge); - dev_dbg(dev, "%s: enable=%s, direction=%d\n", + dev_dbg(ctx->dev, "%s: enable=%s, direction=%d\n", __func__, enable ? "true" : "false", direction); mutex_lock(&ctx->lock); @@ -1436,64 +1422,28 @@ static int it66121_audio_mute(struct device *dev, void *data, return ret; } -static int it66121_audio_get_eld(struct device *dev, void *data, - u8 *buf, size_t len) -{ - struct it66121_ctx *ctx = dev_get_drvdata(dev); - - mutex_lock(&ctx->lock); - if (!ctx->connector) { - /* Pass en empty ELD if connector not available */ - dev_dbg(dev, "No connector present, passing empty EDID data"); - memset(buf, 0, len); - } else { - mutex_lock(&ctx->connector->eld_mutex); - memcpy(buf, ctx->connector->eld, - min(sizeof(ctx->connector->eld), len)); - mutex_unlock(&ctx->connector->eld_mutex); - } - mutex_unlock(&ctx->lock); - - return 0; -} - -static const struct hdmi_codec_ops it66121_audio_codec_ops = { - .hw_params = it66121_audio_hw_params, - .audio_startup = it66121_audio_startup, - .audio_shutdown = it66121_audio_shutdown, - .mute_stream = it66121_audio_mute, - .get_eld = it66121_audio_get_eld, +static const struct drm_bridge_funcs it66121_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .attach = it66121_bridge_attach, + .atomic_get_output_bus_fmts = it66121_bridge_atomic_get_output_bus_fmts, + .atomic_get_input_bus_fmts = it66121_bridge_atomic_get_input_bus_fmts, + .atomic_enable = it66121_bridge_enable, + .atomic_disable = it66121_bridge_disable, + .atomic_check = it66121_bridge_check, + .mode_set = it66121_bridge_mode_set, + .mode_valid = it66121_bridge_mode_valid, + .detect = it66121_bridge_detect, + .edid_read = it66121_bridge_edid_read, + .hpd_enable = it66121_bridge_hpd_enable, + .hpd_disable = it66121_bridge_hpd_disable, + .hdmi_audio_startup = it66121_hdmi_audio_startup, + .hdmi_audio_prepare = it66121_hdmi_audio_prepare, + .hdmi_audio_shutdown = it66121_hdmi_audio_shutdown, + .hdmi_audio_mute_stream = it66121_hdmi_audio_mute_stream, }; -static int it66121_audio_codec_init(struct it66121_ctx *ctx, struct device *dev) -{ - struct hdmi_codec_pdata codec_data = { - .ops = &it66121_audio_codec_ops, - .i2s = 1, /* Only i2s support for now */ - .spdif = 0, - .max_i2s_channels = 8, - .no_capture_mute = 1, - }; - - if (!of_property_present(dev->of_node, "#sound-dai-cells")) { - dev_info(dev, "No \"#sound-dai-cells\", no audio\n"); - return 0; - } - - ctx->audio.pdev = platform_device_register_data(dev, - HDMI_CODEC_DRV_NAME, - PLATFORM_DEVID_AUTO, - &codec_data, - sizeof(codec_data)); - - if (IS_ERR(ctx->audio.pdev)) { - dev_err(dev, "Failed to initialize HDMI audio codec: %d\n", - PTR_ERR_OR_ZERO(ctx->audio.pdev)); - } - - return PTR_ERR_OR_ZERO(ctx->audio.pdev); -} - static const char * const it66121_supplies[] = { "vcn33", "vcn18", "vrf12" }; @@ -1602,7 +1552,15 @@ static int it66121_probe(struct i2c_client *client) } } - it66121_audio_codec_init(ctx, dev); + if (of_property_present(dev->of_node, "#sound-dai-cells")) { + ctx->bridge.ops |= DRM_BRIDGE_OP_HDMI_AUDIO; + ctx->bridge.hdmi_audio_dev = dev; + ctx->bridge.hdmi_audio_max_i2s_playback_channels = 8; + /* of-graph not supported, phandle match only */ + ctx->bridge.hdmi_audio_dai_port = -1; + } else { + dev_info(dev, "No \"#sound-dai-cells\", no audio\n"); + } drm_bridge_add(&ctx->bridge);