From 4c8b8472f85a730a6853ab68474f210143f42b5a Mon Sep 17 00:00:00 2001 From: Suresh Guttula Date: Tue, 14 Jul 2026 20:20:39 +0530 Subject: drm/amdgpu : update mmhub eco sec lvl for vcn5_3 This patch requests PSP to set the sec lvl for vcn and jpeg. Signed-off-by: Suresh Guttula Reviewed-by: McRae Geoffrey Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c | 27 +++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h | 1 + drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c | 24 ++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/psp_gfx_if.h | 1 + drivers/gpu/drm/amd/amdgpu/vcn_v5_0_0.c | 26 ++++++++++++++++++++++++++ 5 files changed, 79 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c index e0c0d7872e45..ca80b6194cac 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c @@ -1222,6 +1222,33 @@ int psp_memory_partition(struct psp_context *psp, int mode) return ret; } +int psp_set_mmhub_eco_sec_level(struct amdgpu_device *adev) +{ + int ret; + struct psp_context *psp = &adev->psp; + struct psp_gfx_cmd_resp *cmd = acquire_psp_cmd_buf(psp); + + cmd->cmd_id = GFX_CMD_ID_SET_MMHUB_ECO_SEC_LEVEL; + + ret = psp_cmd_submit_buf(psp, NULL, cmd, psp->fence_buf_mc_addr); + if (ret) { + dev_err(psp->adev->dev, + "PSP request failed to set mmuhub eco sec level with ret=%d\n", ret); + release_psp_cmd_buf(psp); + return ret; + } + + if (cmd->resp.status) { + dev_err(psp->adev->dev, + "MMHUB ECO SEC LEVEL command 0x%x failed, PSP response status: 0x%X\n", + cmd->cmd_id, cmd->resp.status); + ret = -EIO; + } + release_psp_cmd_buf(psp); + + return ret; +} + static int psp_ptl_fmt_verify(struct psp_context *psp, enum amdgpu_ptl_fmt fmt, uint32_t *ptl_fmt) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h index 2d838b1b2b11..3d7744d60fac 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h @@ -656,5 +656,6 @@ int amdgpu_psp_reg_program_no_ring(struct psp_context *psp, uint32_t val, void amdgpu_psp_debugfs_init(struct amdgpu_device *adev); int amdgpu_psp_get_fw_type(struct amdgpu_firmware_info *ucode, enum psp_gfx_fw_type *type); +int psp_set_mmhub_eco_sec_level(struct amdgpu_device *adev); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c index 66d4c487eee5..68152b2ce015 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c @@ -399,6 +399,25 @@ static void jpeg_v5_3_0_stop_dpg_mode(struct amdgpu_device *adev, int inst_idx) WREG32_SOC15(JPEG, inst_idx, regUVD_JPEG_POWER_STATUS, reg_data); } +/** + * jpeg_v5_3_0_set_mmhub_eco_sec_level - set jpeg sec lvl reg + * + * @adev: amdgpu_device pointer + * + * request psp to set secure lvl + */ +static int jpeg_v5_3_0_set_mmhub_eco_sec_level(struct amdgpu_device *adev) +{ + int r = 0; + + if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) { + /* Request to PSP to program JPEG secure lvl */ + r = psp_set_mmhub_eco_sec_level(adev); + } + + return r; +} + /** * jpeg_v5_3_0_start - start JPEG block * @@ -424,6 +443,11 @@ static int jpeg_v5_3_0_start(struct amdgpu_device *adev) if (r) return r; + /* program JPEG secure lvl register */ + r = jpeg_v5_3_0_set_mmhub_eco_sec_level(adev); + if (r) + return r; + /* JPEG disable CGC */ jpeg_v5_3_0_disable_clock_gating(adev); diff --git a/drivers/gpu/drm/amd/amdgpu/psp_gfx_if.h b/drivers/gpu/drm/amd/amdgpu/psp_gfx_if.h index ac34bac3c839..ad618b78685f 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_gfx_if.h +++ b/drivers/gpu/drm/amd/amdgpu/psp_gfx_if.h @@ -110,6 +110,7 @@ enum psp_gfx_cmd_id GFX_CMD_ID_PERF_HW = 0x0000004C, /* performance monitor */ GFX_CMD_ID_FB_FW_RESERV_ADDR = 0x00000050, /* Query FW reservation addr */ GFX_CMD_ID_FB_FW_RESERV_EXT_ADDR = 0x00000051, /* Query FW reservation extended addr */ + GFX_CMD_ID_SET_MMHUB_ECO_SEC_LEVEL = 0x0000005D, /* Set MMHUB ECO sec lvls on VCN block */ }; /* PSP boot config sub-commands */ diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_0.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_0.c index 45580e9c4e0c..886e2c2074a9 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_0.c @@ -795,6 +795,27 @@ static int vcn_v5_0_0_start_dpg_mode(struct amdgpu_vcn_inst *vinst, return 0; } +/** + * vcn_v5_0_0_set_mmhub_eco_sec_level - set vcn sec lvl reg + * + * @adev: amdgpu_device pointer + * + * request psp to set sec lvl + */ +static int vcn_v5_0_0_set_mmhub_eco_sec_level(struct amdgpu_device *adev) +{ + int r = 0; + + if (amdgpu_ip_version(adev, VCN_HWIP, 0) == IP_VERSION(5, 3, 0)) { + if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) { + /* Request to PSP to program VCN secure lvl */ + r = psp_set_mmhub_eco_sec_level(adev); + } + } + + return r; +} + /** * vcn_v5_0_0_start - VCN start * @@ -819,6 +840,11 @@ static int vcn_v5_0_0_start(struct amdgpu_vcn_inst *vinst) fw_shared = adev->vcn.inst[i].fw_shared.cpu_addr; + /* program VCN secure lvl register */ + r = vcn_v5_0_0_set_mmhub_eco_sec_level(adev); + if (r) + return r; + if (adev->pg_flags & AMD_PG_SUPPORT_VCN_DPG) return vcn_v5_0_0_start_dpg_mode(vinst, adev->vcn.inst[i].indirect_sram); -- cgit v1.2.3 From a947c8662ef9a5fe16eccf84fbe1b17c7ad97dc3 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Fri, 10 Jul 2026 16:33:24 +0530 Subject: drm/amdgpu/gfx9.4.3: Fix MEC rollback loop in EOP IRQ setup Fix the error cleanup path in gfx_v9_4_3_set_userq_eop_interrupts() by using the correct loop condition. This ensures all previously enabled EOP IRQs are released if setup fails. Fixes: 832f0aa050ff ("drm/amdgpu/gfx9.4.3: add support for disabling kernel queues") Reported-by: Dan Carpenter Cc: Kent Russell Cc: Alex Deucher Signed-off-by: Srinivasan Shanmugam Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c index 44d38b76cb4f..9512fef81d84 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c @@ -2331,7 +2331,7 @@ err_compute: } } for (xcc_id--; xcc_id >= 0; xcc_id--) { - for (m = adev->gfx.mec.num_mec - 1; m <= 0; m--) { + for (m = adev->gfx.mec.num_mec - 1; m >= 0; m--) { for (p = adev->gfx.mec.num_pipe_per_mec - 1; p >= 0; p--) { irq_type = AMDGPU_CP_IRQ_COMPUTE_MEC1_PIPE0_EOP + (m * adev->gfx.mec.num_pipe_per_mec) + p; -- cgit v1.2.3 From bf1c9c060ee12c84c3c4ae12ce9209bf61290134 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 13 Jul 2026 16:32:49 -0400 Subject: drm/amdgpu: properly account for resets with user queues We need to increment the reset counter, force fence completion, and set the wedged event when a user queue is reset, but only for the guilty queue. We don't want additional events for collateral damage. Only increment the reset counter and set the wedged event in the top level userq reset callers (both KGD and KFD). This aligns with how this is managed for kernel queue resets. v2: fix task info refcount handling, align with kfd error reporting. v3: check for has_reset_queue in KFD path (Prike) Cc: Srinivasan Shanmugam Cc: Prike Liang Cc: Sunil Khatri Cc: Lazar, Lijo Reviewed-by: Srinivasan Shanmugam Reviewed-by: Lijo Lazar Reviewed-by: Prike Liang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 18 +++++++++++++++++- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 2 -- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 19 ++++++++++++++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index babaabe2d891..969f49592450 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -123,6 +123,8 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) struct amdgpu_device *adev = uq_mgr->adev; const struct amdgpu_userq_funcs *userq_funcs = adev->userq_funcs[queue->queue_type]; + struct drm_wedge_task_info *info = NULL; + struct amdgpu_task_info *ti = NULL; bool gpu_reset = false; if (unlikely(adev->debug_disable_gpu_ring_reset)) { @@ -137,6 +139,14 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) if (!amdgpu_gpu_recovery) return; + if (queue->vm && queue->vm->pasid) { + ti = amdgpu_vm_get_task_info_pasid(adev, queue->vm->pasid); + if (ti) { + amdgpu_vm_print_task_info(adev, ti); + info = &ti->task; + } + } + if (amdgpu_userq_is_reset_type_supported(adev, queue->queue_type, AMDGPU_RESET_TYPE_PER_QUEUE)) { int r; @@ -146,11 +156,17 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) queue, NULL, NULL); else r = userq_funcs->reset(queue); - if (r) + if (r) { gpu_reset = true; + } else { + atomic_inc(&adev->gpu_reset_counter); + amdgpu_userq_fence_driver_force_completion(queue); + drm_dev_wedged_event(adev_to_drm(adev), DRM_WEDGE_RECOVERY_NONE, info); + } } else { gpu_reset = true; } + amdgpu_vm_put_task_info(ti); /* * Don't schedule the work here! Scheduling or queue work from one reset diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index e099304b4ac4..df4efdea1136 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -239,9 +239,7 @@ int mes_userq_reset_queue(struct amdgpu_device *adev, r = mes_userq_unmap(uq); if (r) return r; - atomic_inc(&adev->gpu_reset_counter); amdgpu_userq_fence_driver_force_completion(uq); - drm_dev_wedged_event(adev_to_drm(adev), DRM_WEDGE_RECOVERY_NONE, NULL); break; } } diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 5446d89a84b3..51ee9c39104b 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -448,6 +448,9 @@ int kfd_reset_queue_mes(struct device_queue_manager *dqm, int queue_type, static int reset_queues_mes(struct device_queue_manager *dqm, struct queue *q) { struct amdgpu_device *adev = (struct amdgpu_device *)dqm->dev->adev; + struct drm_wedge_task_info *info = NULL; + struct amdgpu_task_info *ti = NULL; + struct kfd_process_device *pdd; unsigned int num_hung = 0; int r = 0; struct mes_remove_queue_input queue_input; @@ -476,13 +479,27 @@ static int reset_queues_mes(struct device_queue_manager *dqm, struct queue *q) r = amdgpu_gfx_reset_mes_compute(adev, NULL, NULL, NULL, &num_hung, &queue_input); if (r) goto fail; + pdd = kfd_get_process_device_data(q->device, q->process); + if (pdd) { + ti = amdgpu_vm_get_task_info_pasid(adev, pdd->pasid); + if (ti) { + amdgpu_vm_print_task_info(adev, ti); + info = &ti->task; + } + } dqm->detect_hang_count = num_hung; /* When MES doesn't detect any queue hang, no reset happens. Don't signal reset * event. */ - if (dqm->detect_hang_count) + if (dqm->detect_hang_count) { kfd_signal_reset_event(dqm->dev); + if (pdd && pdd->has_reset_queue) { + atomic_inc(&adev->gpu_reset_counter); + drm_dev_wedged_event(adev_to_drm(adev), DRM_WEDGE_RECOVERY_NONE, info); + } + } + amdgpu_vm_put_task_info(ti); fail: dqm->detect_hang_count = 0; -- cgit v1.2.3 From 16dda956266f9171491bd647b06b802ae945a726 Mon Sep 17 00:00:00 2001 From: Christian König Date: Wed, 6 May 2026 14:10:27 +0200 Subject: drm/amdgpu: give different sizes for each SA pool type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IMMEDIATE (page fault) and DIRECT (reset) pool should be used only very rarely and by a single thread. Saves roughly 1.25MiB of memory and GART space for each amdgpu device. Signed-off-by: Christian König Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c | 7 ++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c index 63f62c670df5..430665933635 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c @@ -351,6 +351,11 @@ free_fence: */ int amdgpu_ib_pool_init(struct amdgpu_device *adev) { + const int sizes[AMDGPU_IB_POOL_MAX] = { + [AMDGPU_IB_POOL_DELAYED] = SZ_1M, + [AMDGPU_IB_POOL_IMMEDIATE] = SZ_128K, + [AMDGPU_IB_POOL_DIRECT] = SZ_512K + }; int r, i; if (adev->ib_pool_ready) @@ -358,7 +363,7 @@ int amdgpu_ib_pool_init(struct amdgpu_device *adev) for (i = 0; i < AMDGPU_IB_POOL_MAX; i++) { r = amdgpu_sa_bo_manager_init(adev, &adev->ib_pools[i], - AMDGPU_IB_POOL_SIZE, 256, + sizes[i], 256, AMDGPU_GEM_DOMAIN_GTT); if (r) goto error; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h index 9d3934b4f106..b11e9f244b53 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h @@ -68,8 +68,6 @@ enum amdgpu_ring_priority_level { #define to_amdgpu_ring(s) container_of((s), struct amdgpu_ring, sched) -#define AMDGPU_IB_POOL_SIZE (1024 * 1024) - enum amdgpu_ring_type { AMDGPU_RING_TYPE_GFX = AMDGPU_HW_IP_GFX, AMDGPU_RING_TYPE_COMPUTE = AMDGPU_HW_IP_COMPUTE, -- cgit v1.2.3 From fa99528ce50c3cc10ca9effc0fe3a254520f1641 Mon Sep 17 00:00:00 2001 From: Christian König Date: Thu, 7 May 2026 14:55:35 +0200 Subject: drm/amdgpu: add gfp_flags to amdgpu_sa_manager v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure that we use the emmergency reserves for unrecoverable page faults and GPU resets. v2: improve code comments a bit based on Timur's feedback Signed-off-by: Christian König Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c | 22 ++++++++++++++++++++-- drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c | 18 ++++++++++++------ drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h | 3 ++- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c index 430665933635..dca96f71efd1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c @@ -356,6 +356,25 @@ int amdgpu_ib_pool_init(struct amdgpu_device *adev) [AMDGPU_IB_POOL_IMMEDIATE] = SZ_128K, [AMDGPU_IB_POOL_DIRECT] = SZ_512K }; + const gfp_t gfp_flags[AMDGPU_IB_POOL_MAX] = { + /* + * For normal page table updates and recoverable retry faults + * (for SVM), further restricted by the VM eviction lock to not + * wait for memory reclaim. + */ + [AMDGPU_IB_POOL_DELAYED] = GFP_KERNEL, + /* + * For redirecting unrecoverable retry faults to the dummy page + * or set the PRT bits. dma_fence submissions might depend on + * that so we need the emmergency reserves. + */ + [AMDGPU_IB_POOL_IMMEDIATE] = GFP_ATOMIC, + /* + * For IB tests during GPU resets. Only very small and temporary + * allocation to allow dma_fences to signal. + */ + [AMDGPU_IB_POOL_DIRECT] = GFP_ATOMIC + }; int r, i; if (adev->ib_pool_ready) @@ -363,8 +382,7 @@ int amdgpu_ib_pool_init(struct amdgpu_device *adev) for (i = 0; i < AMDGPU_IB_POOL_MAX; i++) { r = amdgpu_sa_bo_manager_init(adev, &adev->ib_pools[i], - sizes[i], 256, - AMDGPU_GEM_DOMAIN_GTT); + sizes[i], gfp_flags[i]); if (r) goto error; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c index 39070b2a4c04..74124f80601e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c @@ -46,11 +46,13 @@ int amdgpu_sa_bo_manager_init(struct amdgpu_device *adev, struct amdgpu_sa_manager *sa_manager, - unsigned int size, u32 suballoc_align, u32 domain) + unsigned int size, gfp_t gfp_flags) { int r; - r = amdgpu_bo_create_kernel(adev, size, AMDGPU_GPU_PAGE_SIZE, domain, + sa_manager->gfp_flags = gfp_flags; + r = amdgpu_bo_create_kernel(adev, size, AMDGPU_GPU_PAGE_SIZE, + AMDGPU_GEM_DOMAIN_GTT, &sa_manager->bo, &sa_manager->gpu_addr, &sa_manager->cpu_ptr); if (r) { @@ -59,7 +61,8 @@ int amdgpu_sa_bo_manager_init(struct amdgpu_device *adev, } memset(sa_manager->cpu_ptr, 0, size); - drm_suballoc_manager_init(&sa_manager->base, size, suballoc_align); + drm_suballoc_manager_init(&sa_manager->base, size, 256); + return r; } @@ -73,7 +76,8 @@ void amdgpu_sa_bo_manager_fini(struct amdgpu_device *adev, drm_suballoc_manager_fini(&sa_manager->base); - amdgpu_bo_free_kernel(&sa_manager->bo, &sa_manager->gpu_addr, &sa_manager->cpu_ptr); + amdgpu_bo_free_kernel(&sa_manager->bo, &sa_manager->gpu_addr, + &sa_manager->cpu_ptr); } int amdgpu_sa_bo_new(struct amdgpu_sa_manager *sa_manager, @@ -81,7 +85,8 @@ int amdgpu_sa_bo_new(struct amdgpu_sa_manager *sa_manager, unsigned int size) { struct drm_suballoc *sa = drm_suballoc_new(&sa_manager->base, size, - GFP_KERNEL, false, 0); + sa_manager->gfp_flags, + false, 0); if (IS_ERR(sa)) { *sa_bo = NULL; @@ -110,6 +115,7 @@ void amdgpu_sa_bo_dump_debug_info(struct amdgpu_sa_manager *sa_manager, { struct drm_printer p = drm_seq_file_printer(m); - drm_suballoc_dump_debug_info(&sa_manager->base, &p, sa_manager->gpu_addr); + drm_suballoc_dump_debug_info(&sa_manager->base, &p, + sa_manager->gpu_addr); } #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h index 8c85c80fc119..1d1c89348709 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h @@ -35,6 +35,7 @@ struct amdgpu_sa_manager { struct amdgpu_bo *bo; uint64_t gpu_addr; void *cpu_ptr; + gfp_t gfp_flags; }; static inline struct amdgpu_sa_manager * @@ -57,7 +58,7 @@ static inline void *amdgpu_sa_bo_cpu_addr(struct drm_suballoc *sa_bo) int amdgpu_sa_bo_manager_init(struct amdgpu_device *adev, struct amdgpu_sa_manager *sa_manager, - unsigned size, u32 align, u32 domain); + unsigned size, gfp_t gfp_flags); void amdgpu_sa_bo_manager_fini(struct amdgpu_device *adev, struct amdgpu_sa_manager *sa_manager); int amdgpu_sa_bo_manager_start(struct amdgpu_device *adev, -- cgit v1.2.3 From ba4af3ca26b1ceed6cc4c52be3591d270e074979 Mon Sep 17 00:00:00 2001 From: Christian König Date: Fri, 8 May 2026 21:00:08 +0200 Subject: drm/amdgpu: move job parameter to the end in amdgpu_job_alloc() and *_with_ib() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the job output parameter to be the last parameter in the amdgpu_job_alloc() and amdgpu_job_alloc_with_ib() function signature. This aligns with the common kernel coding convention where output parameters typically come last. Assisted-by: Claude:Sonnet 4 Signed-off-by: Christian König Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 4 ++-- drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 5 +++-- drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c | 3 ++- drivers/gpu/drm/amd/amdgpu/amdgpu_job.c | 10 +++++----- drivers/gpu/drm/amd/amdgpu/amdgpu_job.h | 8 ++++---- drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c | 5 +++-- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 11 ++++++----- drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c | 5 +++-- drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c | 8 +++++--- drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c | 12 ++++++++---- drivers/gpu/drm/amd/amdgpu/amdgpu_vm_sdma.c | 2 +- drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c | 10 ++++++---- drivers/gpu/drm/amd/amdgpu/uvd_v7_0.c | 10 ++++++---- drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | 4 ++-- 15 files changed, 57 insertions(+), 42 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index c693c508df1a..a496c383cf6e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -711,7 +711,7 @@ int amdgpu_amdkfd_submit_ib(struct amdgpu_device *adev, goto err; } - ret = amdgpu_job_alloc(adev, NULL, NULL, NULL, 1, &job, 0); + ret = amdgpu_job_alloc(adev, NULL, NULL, NULL, 1, 0, &job); if (ret) goto err; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index d58701908158..2a6859b6ef05 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -267,8 +267,8 @@ static int amdgpu_cs_pass1(struct amdgpu_cs_parser *p, for (i = 0; i < p->gang_size; ++i) { ret = amdgpu_job_alloc(p->adev, vm, p->entities[i], vm, - num_ibs[i], &p->jobs[i], - p->filp->client_id); + num_ibs[i], p->filp->client_id, + &p->jobs[i]); if (ret) goto free_all_kdata; switch (p->adev->enforce_isolation[fpriv->xcp_id]) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c index 1e275c2e7dd3..9d3b40c385c9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -1713,8 +1713,9 @@ static int amdgpu_gfx_run_cleaner_shader_job(struct amdgpu_ring *ring) owner = (void *)(unsigned long)atomic_inc_return(&counter); r = amdgpu_job_alloc_with_ib(ring->adev, &entity, owner, - ib_size_dw * sizeof(uint32_t), 0, &job, - AMDGPU_KERNEL_JOB_ID_CLEANER_SHADER); + ib_size_dw * sizeof(uint32_t), 0, + AMDGPU_KERNEL_JOB_ID_CLEANER_SHADER, + &job); if (r) goto err; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c index 4000b2c6fc98..3da50132c5d0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c @@ -761,7 +761,8 @@ void amdgpu_gmc_flush_gpu_tlb(struct amdgpu_device *adev, uint32_t vmid, r = amdgpu_job_alloc_with_ib(ring->adev, &adev->mman.default_entity.base, AMDGPU_FENCE_OWNER_UNDEFINED, 16 * 4, AMDGPU_IB_POOL_IMMEDIATE, - &job, AMDGPU_KERNEL_JOB_ID_FLUSH_GPU_TLB); + AMDGPU_KERNEL_JOB_ID_FLUSH_GPU_TLB, + &job); if (r) goto error_alloc; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c index cff73f1b5a72..fa695bdcf6eb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c @@ -198,8 +198,8 @@ exit: int amdgpu_job_alloc(struct amdgpu_device *adev, struct amdgpu_vm *vm, struct drm_sched_entity *entity, void *owner, - unsigned int num_ibs, struct amdgpu_job **job, - u64 drm_client_id) + unsigned int num_ibs, u64 drm_client_id, + struct amdgpu_job **job) { struct amdgpu_fence *af; int r; @@ -252,12 +252,12 @@ err_job: int amdgpu_job_alloc_with_ib(struct amdgpu_device *adev, struct drm_sched_entity *entity, void *owner, size_t size, enum amdgpu_ib_pool_type pool_type, - struct amdgpu_job **job, u64 k_job_id) + u64 k_job_id, struct amdgpu_job **job) { int r; - r = amdgpu_job_alloc(adev, NULL, entity, owner, 1, job, - k_job_id); + r = amdgpu_job_alloc(adev, NULL, entity, owner, 1, k_job_id, + job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h index e70a1117b812..822849693b3a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h @@ -115,13 +115,13 @@ static inline struct amdgpu_ring *amdgpu_job_ring(struct amdgpu_job *job) int amdgpu_job_alloc(struct amdgpu_device *adev, struct amdgpu_vm *vm, struct drm_sched_entity *entity, void *owner, - unsigned int num_ibs, struct amdgpu_job **job, - u64 drm_client_id); + unsigned int num_ibs, u64 drm_client_id, + struct amdgpu_job **job); int amdgpu_job_alloc_with_ib(struct amdgpu_device *adev, struct drm_sched_entity *entity, void *owner, size_t size, enum amdgpu_ib_pool_type pool_type, - struct amdgpu_job **job, - u64 k_job_id); + u64 k_job_id, + struct amdgpu_job **job); void amdgpu_job_set_resources(struct amdgpu_job *job, struct amdgpu_bo *gds, struct amdgpu_bo *gws, struct amdgpu_bo *oa); void amdgpu_job_free_resources(struct amdgpu_job *job); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c index 57935c321515..363b99168478 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c @@ -197,8 +197,9 @@ static int amdgpu_jpeg_dec_set_reg(struct amdgpu_ring *ring, uint32_t handle, int i, r; r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, - AMDGPU_IB_POOL_DIRECT, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DIRECT, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 7920675af1d1..03c1e5e3580c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -244,8 +244,9 @@ static int amdgpu_ttm_map_buffer(struct amdgpu_ttm_buffer_entity *entity, r = amdgpu_job_alloc_with_ib(adev, &entity->base, AMDGPU_FENCE_OWNER_UNDEFINED, num_dw * 4 + num_bytes, - AMDGPU_IB_POOL_DELAYED, &job, - AMDGPU_KERNEL_JOB_ID_TTM_MAP_BUFFER); + AMDGPU_IB_POOL_DELAYED, + AMDGPU_KERNEL_JOB_ID_TTM_MAP_BUFFER, + &job); if (r) return r; @@ -1592,8 +1593,8 @@ static int amdgpu_ttm_access_memory_sdma(struct ttm_buffer_object *bo, r = amdgpu_job_alloc_with_ib(adev, &adev->mman.default_entity.base, AMDGPU_FENCE_OWNER_UNDEFINED, num_dw * 4, AMDGPU_IB_POOL_DELAYED, - &job, - AMDGPU_KERNEL_JOB_ID_TTM_ACCESS_MEMORY_SDMA); + AMDGPU_KERNEL_JOB_ID_TTM_ACCESS_MEMORY_SDMA, + &job); if (r) goto out; @@ -2443,7 +2444,7 @@ static int amdgpu_ttm_prepare_job(struct amdgpu_device *adev, int r; r = amdgpu_job_alloc_with_ib(adev, &entity->base, AMDGPU_FENCE_OWNER_UNDEFINED, - num_dw * 4, pool, job, k_job_id); + num_dw * 4, pool, k_job_id, job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c index 23383ac5323f..e8b0c62f72be 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c @@ -1165,8 +1165,9 @@ static int amdgpu_uvd_send_msg(struct amdgpu_ring *ring, struct amdgpu_bo *bo, r = amdgpu_job_alloc_with_ib(ring->adev, &adev->uvd.entity, AMDGPU_FENCE_OWNER_UNDEFINED, 64, direct ? AMDGPU_IB_POOL_DIRECT : - AMDGPU_IB_POOL_DELAYED, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DELAYED, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c index eef3c9853a5c..1f7a98cdce5a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c @@ -473,7 +473,8 @@ static int amdgpu_vce_get_create_msg(struct amdgpu_ring *ring, uint32_t handle, r = amdgpu_job_alloc_with_ib(ring->adev, &ring->adev->vce.entity, AMDGPU_FENCE_OWNER_UNDEFINED, ib_size_dw * 4, AMDGPU_IB_POOL_DIRECT, - &job, AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; @@ -564,8 +565,9 @@ static int amdgpu_vce_get_destroy_msg(struct amdgpu_ring *ring, uint32_t handle, AMDGPU_FENCE_OWNER_UNDEFINED, ib_size_dw * 4, direct ? AMDGPU_IB_POOL_DIRECT : - AMDGPU_IB_POOL_DELAYED, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DELAYED, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c index fe504f1a3fc8..17db7264269e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c @@ -629,7 +629,8 @@ static int amdgpu_vcn_dec_send_msg(struct amdgpu_ring *ring, r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, 64, AMDGPU_IB_POOL_DIRECT, - &job, AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) goto err; @@ -809,7 +810,8 @@ static int amdgpu_vcn_dec_sw_send_msg(struct amdgpu_ring *ring, r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, AMDGPU_IB_POOL_DIRECT, - &job, AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) goto err; @@ -939,7 +941,8 @@ static int amdgpu_vcn_enc_get_create_msg(struct amdgpu_ring *ring, uint32_t hand r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, AMDGPU_IB_POOL_DIRECT, - &job, AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; @@ -1006,7 +1009,8 @@ static int amdgpu_vcn_enc_get_destroy_msg(struct amdgpu_ring *ring, uint32_t han r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, AMDGPU_IB_POOL_DIRECT, - &job, AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_sdma.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_sdma.c index fd09a2b5a147..430274237689 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_sdma.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm_sdma.c @@ -56,7 +56,7 @@ static int amdgpu_vm_sdma_alloc_job(struct amdgpu_vm_update_params *p, ndw = min(ndw, AMDGPU_VM_SDMA_MAX_NUM_DW); r = amdgpu_job_alloc_with_ib(p->adev, entity, AMDGPU_FENCE_OWNER_VM, - ndw * 4, pool, &p->job, k_job_id); + ndw * 4, pool, k_job_id, &p->job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c b/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c index 8bb9592b0981..bbd0e05d39e5 100644 --- a/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c @@ -217,8 +217,9 @@ static int uvd_v6_0_enc_get_create_msg(struct amdgpu_ring *ring, uint32_t handle int i, r; r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, - AMDGPU_IB_POOL_DIRECT, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DIRECT, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; @@ -282,8 +283,9 @@ static int uvd_v6_0_enc_get_destroy_msg(struct amdgpu_ring *ring, int i, r; r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, - AMDGPU_IB_POOL_DIRECT, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DIRECT, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/uvd_v7_0.c b/drivers/gpu/drm/amd/amdgpu/uvd_v7_0.c index df2c83348315..46222fc30be6 100644 --- a/drivers/gpu/drm/amd/amdgpu/uvd_v7_0.c +++ b/drivers/gpu/drm/amd/amdgpu/uvd_v7_0.c @@ -225,8 +225,9 @@ static int uvd_v7_0_enc_get_create_msg(struct amdgpu_ring *ring, u32 handle, int i, r; r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, - AMDGPU_IB_POOL_DIRECT, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DIRECT, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; @@ -289,8 +290,9 @@ static int uvd_v7_0_enc_get_destroy_msg(struct amdgpu_ring *ring, u32 handle, int i, r; r = amdgpu_job_alloc_with_ib(ring->adev, NULL, NULL, ib_size_dw * 4, - AMDGPU_IB_POOL_DIRECT, &job, - AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST); + AMDGPU_IB_POOL_DIRECT, + AMDGPU_KERNEL_JOB_ID_VCN_RING_TEST, + &job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c index 7cd236c1ff75..ed3649a81332 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c @@ -68,8 +68,8 @@ svm_migrate_gart_map(struct amdgpu_ring *ring, AMDGPU_FENCE_OWNER_UNDEFINED, num_dw * 4 + num_bytes, AMDGPU_IB_POOL_DELAYED, - &job, - AMDGPU_KERNEL_JOB_ID_KFD_GART_MAP); + AMDGPU_KERNEL_JOB_ID_KFD_GART_MAP, + &job); if (r) return r; -- cgit v1.2.3 From 60bbdfb959ca0390d56595d3e4a717176f7352ee Mon Sep 17 00:00:00 2001 From: Christian König Date: Mon, 11 May 2026 14:46:54 +0200 Subject: drm/amdgpu: use correct gfp_t for job allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For job allocation in GPU reset and page fault handling we must use GFP_ATOMIC to guarantee that we don't cycle back and depend on a dma_fence submission for the memory allocation. Add gfp_flags argument to amdgpu_job_alloc() and expose the gfp_flags of IB pools with amdgpu_ib_pool_gfp_flags() so that we can use different flags when allocating jobs. Assisted-by: Claude:Sonnet 4 Signed-off-by: Christian König Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 3 ++- drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c | 11 +++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_job.c | 9 +++++---- drivers/gpu/drm/amd/amdgpu/amdgpu_job.h | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h | 2 ++ 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index a496c383cf6e..121282dd30c1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -711,7 +711,8 @@ int amdgpu_amdkfd_submit_ib(struct amdgpu_device *adev, goto err; } - ret = amdgpu_job_alloc(adev, NULL, NULL, NULL, 1, 0, &job); + ret = amdgpu_job_alloc(adev, NULL, NULL, NULL, 1, 0, GFP_KERNEL, + &job); if (ret) goto err; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index 2a6859b6ef05..e8eaa3908ca0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -268,7 +268,7 @@ static int amdgpu_cs_pass1(struct amdgpu_cs_parser *p, for (i = 0; i < p->gang_size; ++i) { ret = amdgpu_job_alloc(p->adev, vm, p->entities[i], vm, num_ibs[i], p->filp->client_id, - &p->jobs[i]); + GFP_KERNEL, &p->jobs[i]); if (ret) goto free_all_kdata; switch (p->adev->enforce_isolation[fpriv->xcp_id]) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c index dca96f71efd1..634b3f7a5fff 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c @@ -416,6 +416,17 @@ void amdgpu_ib_pool_fini(struct amdgpu_device *adev) adev->ib_pool_ready = false; } +/** + * amdgpu_ib_pool_gfp_flags - Returns the gfp flags to use for each pool + * @adev: amdgpu device pointer + * @type: the IB pool type + */ +gfp_t amdgpu_ib_pool_gfp_flags(struct amdgpu_device *adev, + enum amdgpu_ib_pool_type type) +{ + return adev->ib_pools[type].gfp_flags; +} + /** * amdgpu_ib_ring_tests - test IBs on the rings * diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c index fa695bdcf6eb..d97cf1212e0f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c @@ -199,7 +199,7 @@ exit: int amdgpu_job_alloc(struct amdgpu_device *adev, struct amdgpu_vm *vm, struct drm_sched_entity *entity, void *owner, unsigned int num_ibs, u64 drm_client_id, - struct amdgpu_job **job) + gfp_t gfp_flags, struct amdgpu_job **job) { struct amdgpu_fence *af; int r; @@ -207,18 +207,18 @@ int amdgpu_job_alloc(struct amdgpu_device *adev, struct amdgpu_vm *vm, if (num_ibs == 0) return -EINVAL; - *job = kzalloc_flex(**job, ibs, num_ibs); + *job = kzalloc_flex(**job, ibs, num_ibs, gfp_flags); if (!*job) return -ENOMEM; - af = kzalloc_obj(struct amdgpu_fence); + af = kzalloc_obj(struct amdgpu_fence, gfp_flags); if (!af) { r = -ENOMEM; goto err_job; } (*job)->hw_fence = af; - af = kzalloc_obj(struct amdgpu_fence); + af = kzalloc_obj(struct amdgpu_fence, gfp_flags); if (!af) { r = -ENOMEM; goto err_fence; @@ -257,6 +257,7 @@ int amdgpu_job_alloc_with_ib(struct amdgpu_device *adev, int r; r = amdgpu_job_alloc(adev, NULL, entity, owner, 1, k_job_id, + amdgpu_ib_pool_gfp_flags(adev, pool_type), job); if (r) return r; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h index 822849693b3a..7e03bd2ff639 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.h @@ -116,7 +116,7 @@ static inline struct amdgpu_ring *amdgpu_job_ring(struct amdgpu_job *job) int amdgpu_job_alloc(struct amdgpu_device *adev, struct amdgpu_vm *vm, struct drm_sched_entity *entity, void *owner, unsigned int num_ibs, u64 drm_client_id, - struct amdgpu_job **job); + gfp_t gfp_flags, struct amdgpu_job **job); int amdgpu_job_alloc_with_ib(struct amdgpu_device *adev, struct drm_sched_entity *entity, void *owner, size_t size, enum amdgpu_ib_pool_type pool_type, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h index b11e9f244b53..015623b8fd05 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h @@ -583,6 +583,8 @@ int amdgpu_ib_schedule(struct amdgpu_ring *ring, unsigned num_ibs, struct dma_fence **f); int amdgpu_ib_pool_init(struct amdgpu_device *adev); void amdgpu_ib_pool_fini(struct amdgpu_device *adev); +gfp_t amdgpu_ib_pool_gfp_flags(struct amdgpu_device *adev, + enum amdgpu_ib_pool_type type); int amdgpu_ib_ring_tests(struct amdgpu_device *adev); bool amdgpu_ring_sched_ready(struct amdgpu_ring *ring); void amdgpu_ring_backup_unprocessed_commands(struct amdgpu_ring *ring, -- cgit v1.2.3 From c53d678cad30450e39534751f94ab9bcb89f714e Mon Sep 17 00:00:00 2001 From: Kent Russell Date: Mon, 6 Jul 2026 12:28:07 -0400 Subject: drm/amdkfd: Clean up debug runlist printing Having single lines with random hex codes really doesn't help a user to know what's going on. Give it a title, and print 8 8-length hex values per line, instead of a single 2-length hex value per printed line. Previous output: ... amdgpu: 0x20000010 amdgpu: 0x3000 amdgpu: 0x12E6E00 amdgpu: 0xFF amdgpu: 0x207008 amdgpu: 0x 0 amdgpu: New output: amdgpu: Runlist dump: amdgpu: 0: 0xc00ea100 0x14008008 0x0f4fffc0 0x00000000 0x20002000 0x00000338 0x00000020 0x00080017 amdgpu: 8: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x03400000 0x00000000 0x00000000 amdgpu: 16: 0xc005a200 0x20000010 0x00003030 0x012ed000 0x000000ff 0x00252008 0x00000000 0xc005a200 amdgpu: 24: 0x20000010 0x0000302c 0x012ec800 0x000000ff 0x0024c008 0x00000000 0xc005a200 0x20000010 amdgpu: 32: 0x00003028 0x012ec000 0x000000ff 0x00246008 0x00000000 0xc005a200 0x20000010 0x00003024 amdgpu: 40: 0x012eb800 0x000000ff 0x00240008 0x00000000 0xc005a200 0x20000010 0x00003020 0x012eb000 amdgpu: 48: 0x000000ff 0x0023a008 0x00000000 0xc005a200 0x20000010 0x0000301c 0x012ea800 0x000000ff amdgpu: 56: 0x00234008 0x00000000 0xc005a200 0x20000010 0x00003018 0x012ea000 0x000000ff 0x0022e008 amdgpu: 64: 0x00000000 0xc005a200 0x20000010 0x00003014 0x012e9800 0x000000ff 0x00228008 0x00000000 amdgpu: 72: 0xc005a200 0x20000010 0x00003010 0x012e9000 0x000000ff 0x00222008 0x00000000 0xc005a200 amdgpu: 80: 0x20000010 0x0000300c 0x012e8800 0x000000ff 0x0021c008 0x00000000 0xc005a200 0x20000010 amdgpu: 88: 0x00003008 0x012e8000 0x000000ff 0x00216008 0x00000000 0xc005a200 0x20000010 0x00003004 amdgpu: 96: 0x012e7800 0x000000ff 0x00210008 0x00000000 0xc005a200 0x20000010 0x00003000 0x012e6e00 amdgpu: 104: 0x000000ff 0x00207008 0x00000000 Signed-off-by: Kent Russell Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_packet_manager.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager.c index b1a6eb349bb3..85bf1790a6c8 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager.c @@ -269,9 +269,19 @@ build_runlist_ib: } pm->is_over_subscription = !!is_over_subscription; - for (i = 0; i < alloc_size_bytes / sizeof(uint32_t); i++) - pr_debug("0x%2X ", rl_buffer[i]); - pr_debug("\n"); + pr_debug("Runlist dump:"); + for (i = 0; i < alloc_size_bytes / sizeof(uint32_t); i += 8) { + char buf[128]; + int j, len = 0; + + /* Dump 8 entries per line with an index for each line */ + len += scnprintf(buf + len, sizeof(buf) - len, "%4u:", i); + + for (j = 0; j < 8 && (i + j) < alloc_size_bytes / sizeof(uint32_t); j++) + len += scnprintf(buf + len, sizeof(buf) - len, " 0x%08x", rl_buffer[i + j]); + + pr_debug("%s\n", buf); + } return retval; } -- cgit v1.2.3 From cd353ecafd69a12b7fa693a7f2dfa450613cc78a Mon Sep 17 00:00:00 2001 From: Will Aitken Date: Wed, 8 Jul 2026 09:09:30 -0400 Subject: drm/amdgpu: Use just sriov capability bit for xgmi ext peer link support The legacy xgmi ta without EXTEND_PEER_LINKS support in sriov still reports it as enabled. It then fails when the command is called. Rely on the host capability bit instead. v2: Replace other instances of supports_ext_link_info with this method. Including sysfs emission for xgmi_port_num. Now amd-smi xgmi --metric will output N/A for all cells when port_nums cannot be mapped in sriov Signed-off-by: Will Aitken Signed-off-by: Victor Skvortsov Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c | 14 +++++++++----- drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c | 6 +++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c index ca80b6194cac..fdcba94ec0d1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c @@ -1888,6 +1888,12 @@ invoke: /* note down the capbility flag for XGMI TA */ psp->xgmi_context.xgmi_ta_caps = xgmi_cmd->caps_flag; + if (!amdgpu_sriov_vf(psp->adev)) + psp->xgmi_context.supports_ext_link_info = psp->xgmi_context.xgmi_ta_caps & + EXTEND_PEER_LINK_INFO_CMD_FLAG; + else + psp->xgmi_context.supports_ext_link_info = amdgpu_sriov_xgmi_ta_ext_peer_link_en(psp->adev); + return ret; } @@ -2065,15 +2071,13 @@ int psp_xgmi_get_topology_info(struct psp_context *psp, amdgpu_ip_version(psp->adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 14) || amdgpu_sriov_vf(psp->adev); - bool ta_port_num_support = psp->xgmi_context.xgmi_ta_caps & EXTEND_PEER_LINK_INFO_CMD_FLAG || - amdgpu_sriov_xgmi_ta_ext_peer_link_en(psp->adev); /* popluate the shared output buffer rather than the cmd input buffer * with node_ids as the input for GET_PEER_LINKS command execution. * This is required for GET_PEER_LINKS per xgmi ta implementation. * The same requirement for GET_EXTEND_PEER_LINKS command. */ - if (ta_port_num_support) { + if (psp->xgmi_context.supports_ext_link_info) { link_extend_info_output = &xgmi_cmd->xgmi_out_message.get_extend_link_info; for (i = 0; i < topology->num_nodes; i++) @@ -2096,7 +2100,7 @@ int psp_xgmi_get_topology_info(struct psp_context *psp, return ret; for (i = 0; i < topology->num_nodes; i++) { - uint8_t node_num_links = ta_port_num_support ? + uint8_t node_num_links = psp->xgmi_context.supports_ext_link_info ? link_extend_info_output->nodes[i].num_links : link_info_output->nodes[i].num_links; /* accumulate num_links on extended data */ if (get_extended_data) { @@ -2106,7 +2110,7 @@ int psp_xgmi_get_topology_info(struct psp_context *psp, topology->nodes[i].num_links : node_num_links; } /* popluate the connected port num info if supported and available */ - if (ta_port_num_support && topology->nodes[i].num_links) { + if (psp->xgmi_context.supports_ext_link_info && topology->nodes[i].num_links) { memcpy(topology->nodes[i].port_num, link_extend_info_output->nodes[i].port_num, sizeof(struct xgmi_connected_port_num) * TA_XGMI__MAX_PORT_NUM); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h index 3d7744d60fac..d80c85793e3b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.h @@ -232,6 +232,7 @@ struct psp_xgmi_context { struct ta_context context; struct psp_xgmi_topology_info top_info; bool supports_extended_data; + bool supports_ext_link_info; uint8_t xgmi_ta_caps; }; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c index c0f581e416f9..2725230aa5e3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c @@ -559,7 +559,7 @@ static int amdgpu_xgmi_sysfs_add_dev_info(struct amdgpu_device *adev, pr_err("failed to create xgmi_num_links\n"); /* Create xgmi port num file if supported */ - if (adev->psp.xgmi_context.xgmi_ta_caps & EXTEND_PEER_LINK_INFO_CMD_FLAG) { + if (adev->psp.xgmi_context.supports_ext_link_info) { ret = device_create_file(adev->dev, &dev_attr_xgmi_port_num); if (ret) dev_err(adev->dev, "failed to create xgmi_port_num\n"); @@ -595,7 +595,7 @@ remove_file: device_remove_file(adev->dev, &dev_attr_xgmi_error); device_remove_file(adev->dev, &dev_attr_xgmi_num_hops); device_remove_file(adev->dev, &dev_attr_xgmi_num_links); - if (adev->psp.xgmi_context.xgmi_ta_caps & EXTEND_PEER_LINK_INFO_CMD_FLAG) + if (adev->psp.xgmi_context.supports_ext_link_info) device_remove_file(adev->dev, &dev_attr_xgmi_port_num); success: @@ -613,7 +613,7 @@ static void amdgpu_xgmi_sysfs_rem_dev_info(struct amdgpu_device *adev, device_remove_file(adev->dev, &dev_attr_xgmi_error); device_remove_file(adev->dev, &dev_attr_xgmi_num_hops); device_remove_file(adev->dev, &dev_attr_xgmi_num_links); - if (adev->psp.xgmi_context.xgmi_ta_caps & EXTEND_PEER_LINK_INFO_CMD_FLAG) + if (adev->psp.xgmi_context.supports_ext_link_info) device_remove_file(adev->dev, &dev_attr_xgmi_port_num); if (hive->kobj.parent != (&adev->dev->kobj)) -- cgit v1.2.3 From 48e1d1e6e8798aef0312e68d8e586021b5b3cf4d Mon Sep 17 00:00:00 2001 From: William Palacek Date: Mon, 6 Jul 2026 10:25:58 -0400 Subject: drm/amdkfd: Add bounds check for CRAT subtype length The CRAT parser validates that the subtype header fits within the image, but does not verify that the advertised subtype length fits. A malformed CRAT table with an oversized length field causes out-of-bounds reads when kfd_parse_subtype() casts the header to specific subtype structures. Add validation that sub_type_hdr + length does not exceed the image boundary before parsing the subtype contents. Signed-off-by: William Palacek Reviewed-by: Alysa Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_crat.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c index 2a239f45fc24..6e0df685503d 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_crat.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_crat.c @@ -1412,6 +1412,15 @@ int kfd_parse_crat_table(void *crat_image, struct list_head *device_list, break; } + /* Validate subtype fits within remaining image */ + if ((char *)sub_type_hdr + sub_type_hdr->length > + (char *)crat_image + image_len) { + pr_warn("CRAT subtype length %u exceeds image bounds\n", + sub_type_hdr->length); + ret = -EINVAL; + break; + } + if (sub_type_hdr->flags & CRAT_SUBTYPE_FLAGS_ENABLED) { ret = kfd_parse_subtype(sub_type_hdr, device_list); if (ret) -- cgit v1.2.3 From 7264bc10c7c657a54603c7fc058bf8e15f18ce12 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Wed, 15 Jul 2026 19:32:56 +0530 Subject: drm/amdgpu: dont pin wptr bo instead use eviction fence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of pinning the wptr bo attach the eviction fence to the bo to make sure it remains valid all the time. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 19 +++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 17 +++++------------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 969f49592450..3fe10d6af757 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1001,6 +1001,7 @@ amdgpu_userq_vm_validate_and_restore_queue(struct amdgpu_userq_mgr *uq_mgr) struct amdgpu_vm *vm = &fpriv->vm; unsigned long key, tmp_key; struct amdgpu_bo_va *bo_va; + struct amdgpu_usermode_queue *queue; struct amdgpu_bo *bo; struct drm_exec exec; struct xarray xa; @@ -1116,6 +1117,24 @@ retry_lock: dma_fence_wait(bo_va->last_pt_update, false); dma_fence_wait(vm->last_update, false); + xa_for_each(&uq_mgr->userq_xa, tmp_key, queue) { + bo = queue->wptr_obj.obj; + if (!bo) { + ret = -EINVAL; + goto unlock_all; + } + + ret = amdgpu_ttm_alloc_gart(&bo->tbo); + if (unlikely(ret)) { + drm_file_err(uq_mgr->file, + "failed to bind wptr bo to gart on resume, qid=%lu ret=%d\n", + tmp_key, ret); + goto unlock_all; + } + + queue->wptr_obj.gpu_addr = amdgpu_bo_gpu_offset(bo); + } + ret = amdgpu_evf_mgr_rearm(&fpriv->evf_mgr, &exec); if (ret) { drm_file_err(uq_mgr->file, "Failed to replace eviction fence\n"); diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index df4efdea1136..639e4a37f872 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -70,27 +70,23 @@ mes_userq_create_wptr_mapping(struct amdgpu_device *adev, ret = -EINVAL; goto fail_map; } - - /* TODO use eviction fence instead of pinning. */ - ret = amdgpu_bo_pin(wptr_obj->obj, AMDGPU_GEM_DOMAIN_GTT); + /* Keep WPTR BO under eviction-fence control instead of pinning. */ + ret = amdgpu_evf_mgr_attach_fence(&uq_mgr_to_fpriv(uq_mgr)->evf_mgr, wptr_obj->obj); if (ret) { - DRM_ERROR("Failed to pin wptr bo. ret %d\n", ret); + DRM_ERROR("Failed to attach eviction fence to wptr bo. ret %d\n", ret); goto fail_map; } ret = amdgpu_ttm_alloc_gart(&wptr_obj->obj->tbo); if (ret) { - DRM_ERROR("Failed to bind bo to GART. ret %d\n", ret); - goto fail_alloc_gart; + DRM_ERROR("Failed to bind wptr bo to GART. ret %d\n", ret); + goto fail_map; } queue->wptr_obj.gpu_addr = amdgpu_bo_gpu_offset(wptr_obj->obj); drm_exec_fini(&exec); return 0; - -fail_alloc_gart: - amdgpu_bo_unpin(wptr_obj->obj); fail_map: amdgpu_bo_unref(&wptr_obj->obj); fail_lock: @@ -511,9 +507,6 @@ static void mes_userq_mqd_destroy(struct amdgpu_usermode_queue *queue) amdgpu_bo_free_kernel(&queue->mqd.obj, &queue->mqd.gpu_addr, &queue->mqd.cpu_ptr); - amdgpu_bo_reserve(queue->wptr_obj.obj, true); - amdgpu_bo_unpin(queue->wptr_obj.obj); - amdgpu_bo_unreserve(queue->wptr_obj.obj); amdgpu_bo_unref(&queue->wptr_obj.obj); } -- cgit v1.2.3 From 8e1f8214cbba2ccda3f3c625df0a67dcfd9b1356 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Thu, 16 Jul 2026 14:36:45 +0200 Subject: drm/radeon: Only define radeon_acpi_vfct_match when actually used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the build failure drivers/gpu/drm/radeon/radeon_bios.c:614:12: error: ‘radeon_acpi_vfct_match’ defined but not used [-Werror=unused-function] 614 | static int radeon_acpi_vfct_match(struct radeon_device *rdev, | ^~~~~~~~~~~~~~~~~~~~~~ for builds with CONFIG_ACPI unset. Fixes: 4059e2f02c8a ("drm/radeon: Fix VFCT bus number matching with soft filter") Signed-off-by: Uwe Kleine-König Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/radeon_bios.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c index 215e47c94d29..8595f4c6e2e5 100644 --- a/drivers/gpu/drm/radeon/radeon_bios.c +++ b/drivers/gpu/drm/radeon/radeon_bios.c @@ -596,6 +596,7 @@ static bool radeon_read_disabled_bios(struct radeon_device *rdev) return legacy_read_disabled_bios(rdev); } +#ifdef CONFIG_ACPI /** * radeon_acpi_vfct_match() - Check if a VFCT entry matches the device * @rdev: Radeon device @@ -635,7 +636,6 @@ static int radeon_acpi_vfct_match(struct radeon_device *rdev, return 0; } -#ifdef CONFIG_ACPI static bool radeon_acpi_vfct_bios(struct radeon_device *rdev) { struct acpi_table_header *hdr; -- cgit v1.2.3 From 8ba869e852d4f1b1c0e5ae9225c77f7ceccbe056 Mon Sep 17 00:00:00 2001 From: Bob Zhou Date: Thu, 16 Jul 2026 10:44:36 +0800 Subject: drm/amdgpu: skip clearing empty freed VM list on GEM close amdgpu_vm_clear_freed() allocates an amdgpu_sync object and walks the VM reservation fences via amdgpu_sync_resv() before checking whether vm->freed has anything to clear. Return early when the list is empty to skip this overhead on a hot path (every GEM close and command submission). Signed-off-by: Bob Zhou Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 157d4168907c..eb6a70fe79ce 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -1553,6 +1553,8 @@ int amdgpu_vm_clear_freed(struct amdgpu_device *adev, struct amdgpu_sync sync; int r; + if (list_empty(&vm->freed)) + return 0; /* * Implicitly sync to command submissions in the same VM before -- cgit v1.2.3 From 84beee0dd2277edecc929e636f168c89a36594b3 Mon Sep 17 00:00:00 2001 From: Gabe Teeger Date: Wed, 15 Jul 2026 11:41:06 -0400 Subject: drm/amd/display: add dcn42b specific SMU clock table read [Why] On DCN42B the SMU reports its DpmClocks table in a different layout than DCN42, with extra clock domains. The clk_mgr was parsing it with the DCN42 layout. [How] Add a DCN42B DpmClocks struct and a dcn42b_get_smu_clocks() that allocates and parses the DCN42B layout, reusing the shared dcn42 helpers so DCN42 is untouched. Populate DCF, SOC, DISP, DPP and FCLK levels and the memory pstate table. Reviewed-by: Charlene Liu Signed-off-by: Gabe Teeger Signed-off-by: Matthew Stewart Signed-off-by: Alex Deucher --- .../amd/display/dc/clk_mgr/dcn42b/dcn42b_clk_mgr.c | 183 +++++++++++++++++++-- 1 file changed, 172 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42b/dcn42b_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42b/dcn42b_clk_mgr.c index 4be5abdc60ea..2c2ba09221e4 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42b/dcn42b_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42b/dcn42b_clk_mgr.c @@ -221,10 +221,6 @@ static void init_clk_states(struct clk_mgr *clk_mgr) clk_mgr->clks.zstate_support = DCN_ZSTATE_SUPPORT_UNKNOWN; } -/* dcn42b_get_dpm_table_from_smu removed: reuse dcn42_get_dpm_table_from_smu. - * Function is identical - only uses SMU calls, no hardware register differences. - */ - void dcn42b_init_clocks(struct clk_mgr *clk_mgr_base) { struct clk_mgr_internal *clk_mgr_int = TO_CLK_MGR_INTERNAL(clk_mgr_base); @@ -412,6 +408,176 @@ static struct clk_mgr_funcs dcn42b_funcs = { .is_smu_present = dcn42_is_smu_present, }; +typedef struct { + uint32_t DcfClocks[NUM_DCFCLK_DPM_LEVELS]; + uint32_t DispClocks[NUM_DISPCLK_DPM_LEVELS]; + uint32_t DppClocks[NUM_DPPCLK_DPM_LEVELS]; + uint32_t SocClocks[NUM_SOCCLK_DPM_LEVELS]; + uint32_t VPEClocks[NUM_VPE_DPM_LEVELS]; + uint32_t FclkClocks_Freq[NUM_FCLK_DPM_LEVELS]; + uint32_t FclkClocks_Voltage[NUM_FCLK_DPM_LEVELS]; + uint32_t SocVoltage[NUM_SOC_VOLTAGE_LEVELS]; + MemPstateTable_t MemPstateTable[NUM_MEM_PSTATE_LEVELS]; + + uint8_t NumDcfClkLevelsEnabled; + uint8_t NumDispClkLevelsEnabled; //Applies to both Dispclk and Dppclk + uint8_t NumSocClkLevelsEnabled; + uint8_t VpeClkLevelsEnabled; + + uint8_t NumMemPstatesEnabled; + uint8_t NumFclkLevelsEnabled; + uint8_t Spare1; + uint8_t Spare2; + + uint8_t Spare3; + uint8_t Spare4; + uint8_t Spare5[2]; + + uint32_t MinGfxClk; + uint32_t MaxGfxClk; + + uint32_t Spare6[8]; + uint32_t Spare7[8]; + uint32_t Spare8[8]; + uint32_t Spare9[8]; +} DpmClocks_t_dcn42b; +struct dcn42b_smu_dpm_clks { + DpmClocks_t_dcn42b *dpm_clks; + union large_integer mc_address; +}; + +static void dcn42b_get_dpm_table_from_smu(struct clk_mgr_internal *clk_mgr, + struct dcn42b_smu_dpm_clks *smu_dpm_clks) +{ + DpmClocks_t_dcn42b *table = smu_dpm_clks->dpm_clks; + + if (!clk_mgr->smu_ver) + return; + + if (!table || smu_dpm_clks->mc_address.quad_part == 0) + return; + + memset(table, 0, sizeof(*table)); + + dcn42_smu_set_dram_addr_high(clk_mgr, + smu_dpm_clks->mc_address.high_part); + dcn42_smu_set_dram_addr_low(clk_mgr, + smu_dpm_clks->mc_address.low_part); + dcn42_smu_transfer_dpm_table_smu_2_dram(clk_mgr); +} + +static void dcn42b_get_smu_clocks(struct clk_mgr_internal *clk_mgr_int) +{ + struct clk_mgr *clk_mgr_base = &clk_mgr_int->base; + struct dcn42b_smu_dpm_clks smu_dpm_clks = { 0 }; + + DC_LOGGER_INIT(clk_mgr_base->ctx->logger); + (void)dc_logger; + + smu_dpm_clks.dpm_clks = (DpmClocks_t_dcn42b *)dm_helpers_allocate_gpu_mem( + clk_mgr_base->ctx, + DC_MEM_ALLOC_TYPE_GART, + sizeof(DpmClocks_t_dcn42b), + &smu_dpm_clks.mc_address.quad_part); + + ASSERT(smu_dpm_clks.dpm_clks); + if (clk_mgr_base->ctx->dc->debug.pstate_enabled && smu_dpm_clks.mc_address.quad_part != 0) { + int i; + DpmClocks_t_dcn42b *dpm_clks = smu_dpm_clks.dpm_clks; + + dcn42b_get_dpm_table_from_smu(clk_mgr_int, &smu_dpm_clks); + DC_LOG_SMU("NumDcfClkLevelsEnabled: %d\n" + "NumDispClkLevelsEnabled: %d\n" + "NumSocClkLevelsEnabled: %d\n" + "VpeClkLevelsEnabled: %d\n" + "FClkLevelsEnabled: %d\n" + "NumMemPstatesEnabled: %d\n" + "MinGfxClk: %d\n" + "MaxGfxClk: %d\n", + dpm_clks->NumDcfClkLevelsEnabled, + dpm_clks->NumDispClkLevelsEnabled, + dpm_clks->NumSocClkLevelsEnabled, + dpm_clks->VpeClkLevelsEnabled, + dpm_clks->NumFclkLevelsEnabled, + dpm_clks->NumMemPstatesEnabled, + dpm_clks->MinGfxClk, + dpm_clks->MaxGfxClk); + + for (i = 0; i < NUM_DCFCLK_DPM_LEVELS; i++) + DC_LOG_SMU("dpm_clks->DcfClocks[%d] = %d\n", i, dpm_clks->DcfClocks[i]); + for (i = 0; i < NUM_DISPCLK_DPM_LEVELS; i++) + DC_LOG_SMU("dpm_clks->DispClocks[%d] = %d\n", i, dpm_clks->DispClocks[i]); + for (i = 0; i < NUM_SOCCLK_DPM_LEVELS; i++) + DC_LOG_SMU("dpm_clks->SocClocks[%d] = %d\n", i, dpm_clks->SocClocks[i]); + for (i = 0; i < NUM_FCLK_DPM_LEVELS; i++) { + DC_LOG_SMU("dpm_clks->FclkClocks_Freq[%d] = %d\n", i, dpm_clks->FclkClocks_Freq[i]); + DC_LOG_SMU("dpm_clks->FclkClocks_Voltage[%d] = %d\n", i, dpm_clks->FclkClocks_Voltage[i]); + } + for (i = 0; i < NUM_SOCCLK_DPM_LEVELS; i++) + DC_LOG_SMU("dpm_clks->SocVoltage[%d] = %d\n", i, dpm_clks->SocVoltage[i]); + + for (i = 0; i < NUM_MEM_PSTATE_LEVELS; i++) + DC_LOG_SMU("dpm_clks.MemPstateTable[%d].UClk = %d MemClk = %d Voltage = %d\n", + i, dpm_clks->MemPstateTable[i].UClk, dpm_clks->MemPstateTable[i].MemClk, dpm_clks->MemPstateTable[i].Voltage); + + if (clk_mgr_base->ctx->dc_bios->integrated_info && clk_mgr_base->ctx->dc->config.use_default_clock_table == false) { + /* DCFCLK */ + dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].dcfclk_mhz, + dpm_clks->DcfClocks, + dpm_clks->NumDcfClkLevelsEnabled); + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dcfclk_levels = dpm_clks->NumDcfClkLevelsEnabled; + + /* SOCCLK */ + dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].socclk_mhz, + dpm_clks->SocClocks, + dpm_clks->NumSocClkLevelsEnabled); + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_socclk_levels = dpm_clks->NumSocClkLevelsEnabled; + + /* DISPCLK */ + dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].dispclk_mhz, + dpm_clks->DispClocks, + dpm_clks->NumDispClkLevelsEnabled); + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dispclk_levels = dpm_clks->NumDispClkLevelsEnabled; + + /* DPPCLK */ + dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].dppclk_mhz, + dpm_clks->DppClocks, + dpm_clks->NumDispClkLevelsEnabled); + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dppclk_levels = dpm_clks->NumDispClkLevelsEnabled; + + /* FCLK */ + dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].fclk_mhz, + dpm_clks->FclkClocks_Freq, + NUM_FCLK_DPM_LEVELS); + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_fclk_levels = dpm_clks->NumFclkLevelsEnabled; + clk_mgr_base->bw_params->clk_table.num_entries = dpm_clks->NumFclkLevelsEnabled; + + /* Memory Pstate table is in reverse order for dcn42. Proper way to map this is for pmfw to provide an fclk indexed uclk key since we consume fclk matched pairs.*/ + ASSERT(dpm_clks->NumMemPstatesEnabled <= NUM_MEM_PSTATE_LEVELS); + if (dpm_clks->NumMemPstatesEnabled > NUM_MEM_PSTATE_LEVELS) + dpm_clks->NumMemPstatesEnabled = NUM_MEM_PSTATE_LEVELS; + for (i = 0; i < dpm_clks->NumDcfClkLevelsEnabled; i++) { + if (i < dpm_clks->NumMemPstatesEnabled) { + clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].memclk_mhz = dpm_clks->MemPstateTable[i].MemClk; + clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[i].WckRatio); + } else { + clk_mgr_base->bw_params->clk_table.entries[i].memclk_mhz = dpm_clks->MemPstateTable[0].MemClk; + clk_mgr_base->bw_params->clk_table.entries[i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[0].WckRatio); + } + } + + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_memclk_levels = dpm_clks->NumDcfClkLevelsEnabled; + + /* DTBCLK tied off in DCN42B */ + clk_mgr_base->bw_params->clk_table.entries[0].dtbclk_mhz = 0; + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dtbclk_levels = 0; + } + } + if (smu_dpm_clks.dpm_clks && smu_dpm_clks.mc_address.quad_part != 0) + dm_helpers_free_gpu_mem(clk_mgr_base->ctx, DC_MEM_ALLOC_TYPE_GART, + smu_dpm_clks.dpm_clks); +} + void dcn42b_clk_mgr_construct( struct dc_context *ctx, struct clk_mgr_dcn42 *clk_mgr, @@ -473,11 +639,6 @@ void dcn42b_clk_mgr_construct( dcn42b_read_ss_info_from_lut(&clk_mgr->base); clk_mgr->base.base.bw_params = &dcn42b_bw_params; - if (clk_mgr->base.smu_present) { - dcn42_get_smu_clocks(&clk_mgr->base); - //overwrite values from dcn42_get_smu_clocks since dtbclk is tied off in DCN42B - clk_mgr->base.base.bw_params->clk_table.entries[0].dtbclk_mhz = 0; - clk_mgr->base.base.bw_params->clk_table.num_entries_per_clk.num_dtbclk_levels = 0; - clk_mgr->base.base.clks.ref_dtbclk_khz = 0; - } + if (clk_mgr->base.smu_present) + dcn42b_get_smu_clocks(&clk_mgr->base); } -- cgit v1.2.3 From b208b0c446097b2c446a6cf2509050033156d94c Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:14:42 -0400 Subject: drm/amdgpu/imu11: WARN() rather than BUG() There's no need to crash the kernel for this case. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/imu_v11_0.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c b/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c index 05b164f38c97..09dc01a425d1 100644 --- a/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/imu_v11_0.c @@ -377,8 +377,9 @@ static void imu_v11_0_program_rlc_ram(struct amdgpu_device *adev) imu_v11_0_3_program_rlc_ram(adev); break; default: - BUG(); - break; + WARN(1, "Invalid GFX/IMU IP version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return; } //Indicate the contents of the RAM are valid -- cgit v1.2.3 From ac9c3715159b4e8481e3f91c804f881799fd69ad Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Wed, 15 Jul 2026 09:35:12 +0800 Subject: drm/amdgpu/ras: Add debug mask to disable CE logs for uniras Add debug mask to disable kernel logs of RAS correctable errors, including both ACA and CE error counter kernel messages. Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 1 + drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c | 1 + drivers/gpu/drm/amd/ras/rascore/ras.h | 2 ++ drivers/gpu/drm/amd/ras/rascore/ras_aca.c | 17 ++++++++++------- drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c | 2 +- drivers/gpu/drm/amd/ras/rascore/ras_core.c | 9 +++++++++ 6 files changed, 24 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index f5d65bd0ac25..7974f9b7944f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -223,6 +223,7 @@ extern int amdgpu_smartshift_bias; extern int amdgpu_use_xgmi_p2p; extern int amdgpu_mtype_local; extern int amdgpu_enforce_isolation; +extern uint amdgpu_debug_mask; #ifdef CONFIG_HSA_AMD extern int sched_policy; extern bool debug_evictions; diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c index 3b5da8515a20..23ffbbc1f68d 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c @@ -309,6 +309,7 @@ static struct ras_core_context *amdgpu_ras_mgr_create_ras_core(struct amdgpu_dev amdgpu_ras_mgr_eeprom_is_supported(adev); init_config.poison_supported = amdgpu_ras_is_poison_mode_supported(adev); + init_config.ras_debug_mask = amdgpu_debug_mask; amdgpu_ras_mgr_init_aca_config(adev, &init_config); amdgpu_ras_mgr_init_eeprom_config(adev, &init_config); diff --git a/drivers/gpu/drm/amd/ras/rascore/ras.h b/drivers/gpu/drm/amd/ras/rascore/ras.h index 878dfdfcb18a..88b0bac158af 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras.h @@ -301,6 +301,7 @@ struct ras_core_config { bool poison_supported; bool ras_eeprom_supported; + uint ras_debug_mask; const struct ras_sys_func *sys_fn; struct ras_aca_config aca_cfg; @@ -403,4 +404,5 @@ int ras_core_convert_soc_pa_to_cur_nps_pages(struct ras_core_context *ras_core, int ras_core_check_address_sanity(struct ras_core_context *ras_core, uint64_t addr); int ras_core_set_debug_mode(struct ras_core_context *ras_core, bool enable); +bool ras_core_is_ce_log_disabled(struct ras_core_context *ras_core); #endif diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_aca.c b/drivers/gpu/drm/amd/ras/rascore/ras_aca.c index 67a35409ff0e..6f57b5d09cd3 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_aca.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_aca.c @@ -83,22 +83,20 @@ static void aca_report_ecc_info(struct ras_core_context *ras_core, seq_no, skt, aid, ecc_count.total_ue_count, blk_name(blk)); } - if (ecc_count.new_de_count) { + if (ecc_count.new_de_count && blk == RAS_BLOCK_ID__UMC) { RAS_DEV_INFO(ras_core->dev, - "{%llu} socket: %d, die: %d, %u new %s detected in %s block\n", + "{%llu} socket: %d, die: %d, %u new deferred hardware errors detected in %s block\n", seq_no, skt, aid, ecc_count.new_de_count, - (blk == RAS_BLOCK_ID__UMC) ? - "deferred hardware errors" : "poison consumption", blk_name(blk)); RAS_DEV_INFO(ras_core->dev, - "{%llu} socket: %d, die: %d, %u %s detected in total in %s block\n", + "{%llu} socket: %d, die: %d, %u deferred hardware errors detected in total in %s block\n", seq_no, skt, aid, ecc_count.total_de_count, - (blk == RAS_BLOCK_ID__UMC) ? - "deferred hardware errors" : "poison consumption", blk_name(blk)); } if (ecc_count.new_ce_count) { + if (ras_core_is_ce_log_disabled(ras_core)) + return; RAS_DEV_INFO(ras_core->dev, "{%llu} socket: %d, die: %d, %u new correctable hardware errors detected in %s block\n", seq_no, skt, aid, ecc_count.new_ce_count, blk_name(blk)); @@ -114,6 +112,11 @@ static void aca_bank_log(struct ras_core_context *ras_core, { int i; + if (ras_core_is_ce_log_disabled(ras_core) && + bank->ecc_type == RAS_ERR_TYPE__CE && + !bank_ecc->de_count) + return; + RAS_DEV_INFO(ras_core->dev, "{%llu}" RAS_HW_ERR "Accelerator Check Architecture events logged\n", bank->seq_no); diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c b/drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c index 840610538c1f..288fad0862a2 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c @@ -265,7 +265,7 @@ static int aca_parse_bank_default(struct ras_core_context *ras_core, ecc->bank_info.addr = bank->regs[ACA_REG_IDX__ADDR]; if (aca_check_bank_is_de(ras_core, status)) { - ecc->de_count = 0; + ecc->de_count = 1; } else { if (bank->ecc_type == RAS_ERR_TYPE__UE) ecc->ue_count = 1; diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_core.c b/drivers/gpu/drm/amd/ras/rascore/ras_core.c index c63a358b7e57..f8a027b60b38 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_core.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_core.c @@ -28,6 +28,10 @@ #define IS_LEAP_YEAR(x) ((x % 4 == 0 && x % 100 != 0) || x % 400 == 0) +enum RAS_DEBUG_MASK { + RAS_DEBUG_DISABLE_RAS_CE_LOG = 9, +}; + static const char * const ras_block_name[] = { "umc", "sdma", @@ -545,6 +549,11 @@ bool ras_core_is_enabled(struct ras_core_context *ras_core) return ras_core->ras_core_enabled; } +bool ras_core_is_ce_log_disabled(struct ras_core_context *ras_core) +{ + return !!(ras_core->config->ras_debug_mask & BIT(RAS_DEBUG_DISABLE_RAS_CE_LOG)); +} + uint64_t ras_core_get_utc_second_timestamp(struct ras_core_context *ras_core) { if (!ras_core) -- cgit v1.2.3 From 636df65409cd769a61031b34eb1040d95005af2b Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Sun, 19 Jul 2026 19:22:55 +0530 Subject: drm/amdgpu: Allow PASID allocator to store fpriv owner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AMDGPU already has a global PASID xarray used for PASID allocation. Allow amdgpu_pasid_alloc() to optionally store the owning DRM file-private object directly. Initial callers pass NULL. A later patch in this series passes the DRM file-private object for DRM PASIDs. This prepares for using: PASID -> fpriv -> VM instead of: PASID -> VM Clear the stored owner from amdgpu_pasid_free_delayed() before waiting for outstanding fences so PASID lookups cannot observe a stale fpriv while the PASID itself is pending delayed release. v6: - Correct the PASID allocator kernel-doc to refer to the XArray cyclic allocator. - Document that PASID owner lookup may return NULL and that the returned fpriv remains valid only while the PASID lock is held. - No code changes. Retain Christian's Reviewed-by tag. v5: - Store NULL instead of xa_mk_value(0) for ownerless PASIDs. - Simplify owner clearing by unconditionally storing NULL. v4: - Add fpriv as an optional parameter to amdgpu_pasid_alloc(). - Drop separate amdgpu_pasid_set_fpriv()/clear_fpriv() helpers. - Clear PASID owner from amdgpu_pasid_free_delayed(). Cc: Alex Deucher Suggested-by: Christian König Signed-off-by: Srinivasan Shanmugam Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c | 85 +++++++++++++++++++++++++++++---- drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h | 6 ++- drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 2 +- 3 files changed, 82 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c index 124fb38eb465..8a2d64f0ebc3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c @@ -51,16 +51,16 @@ struct amdgpu_pasid_cb { /** * amdgpu_pasid_alloc - Allocate a PASID - * @bits: Maximum width of the PASID in bits, must be at least 1 + * @bits: Number of PASID bits supported by the hardware + * @fpriv: DRM file private to associate with the PASID * - * Uses kernel's IDR cyclic allocator (same as PID allocation). + * Uses the kernel's XArray cyclic allocator. * Allocates sequentially with automatic wrap-around. * - * Returns a positive integer on success. Returns %-EINVAL if bits==0. - * Returns %-ENOSPC if no PASID was available. Returns %-ENOMEM on - * memory allocation failure. + * Returns: + * Allocated PASID on success or a negative error code on failure. */ -int amdgpu_pasid_alloc(unsigned int bits) +int amdgpu_pasid_alloc(unsigned int bits, struct amdgpu_fpriv *fpriv) { u32 pasid; int r; @@ -68,9 +68,9 @@ int amdgpu_pasid_alloc(unsigned int bits) if (bits == 0) return -EINVAL; - r = xa_alloc_cyclic_irq(&amdgpu_pasid_xa, &pasid, xa_mk_value(0), - XA_LIMIT(1, (1U << bits) - 1), - &amdgpu_pasid_xa_next, GFP_KERNEL); + r = xa_alloc_cyclic_irq(&amdgpu_pasid_xa, &pasid, fpriv, + XA_LIMIT(1, (1U << bits) - 1), + &amdgpu_pasid_xa_next, GFP_KERNEL); if (r < 0) return r; @@ -106,6 +106,71 @@ static void amdgpu_pasid_free_cb(struct dma_fence *fence, kfree(cb); } +/** + * amdgpu_pasid_clear_owner - Clear the owner associated with a PASID + * @pasid: PASID whose owner should be cleared + * + * Replace the stored owner with NULL while keeping the PASID allocated. + * + * This is used by the delayed PASID free path so that future PASID + * lookups cannot resolve a stale DRM file-private object while the PASID + * is still waiting for outstanding fences before being released. + */ +static void amdgpu_pasid_clear_owner(u32 pasid) +{ + unsigned long flags; + + if (!pasid) + return; + + xa_lock_irqsave(&amdgpu_pasid_xa, flags); + __xa_store(&amdgpu_pasid_xa, pasid, NULL, GFP_ATOMIC); + xa_unlock_irqrestore(&amdgpu_pasid_xa, flags); +} + +/** + * amdgpu_pasid_lock - acquire the global PASID xarray lock + * @flags: storage for interrupt state + * + * Acquire the global PASID xarray lock with interrupts disabled. + * The saved interrupt state must be passed to + * amdgpu_pasid_unlock(). + */ +void amdgpu_pasid_lock(unsigned long *flags) +{ + xa_lock_irqsave(&amdgpu_pasid_xa, *flags); +} + +/** + * amdgpu_pasid_unlock - release the global PASID xarray lock + * @flags: interrupt state returned by amdgpu_pasid_lock() + * + * Release the global PASID xarray lock and restore the previous + * interrupt state. + */ +void amdgpu_pasid_unlock(unsigned long flags) +{ + xa_unlock_irqrestore(&amdgpu_pasid_xa, flags); +} + +/** + * amdgpu_pasid_get_fpriv_locked - get the DRM owner of a PASID + * @pasid: PASID to resolve + * + * The caller must hold the PASID XArray lock. + * + * Returns: + * Pointer to the owning DRM file private, or %NULL if the PASID has no + * current owner. + * + * The returned pointer is only valid while the PASID lock remains held + * and must not be retained after calling amdgpu_pasid_unlock(). + */ +struct amdgpu_fpriv *amdgpu_pasid_get_fpriv_locked(u32 pasid) +{ + return xa_load(&amdgpu_pasid_xa, pasid); +} + /** * amdgpu_pasid_free_delayed - free pasid when fences signal * @@ -121,6 +186,8 @@ void amdgpu_pasid_free_delayed(struct dma_resv *resv, struct dma_fence *fence; int r; + amdgpu_pasid_clear_owner(pasid); + r = dma_resv_get_singleton(resv, DMA_RESV_USAGE_BOOKKEEP, &fence); if (r) goto fallback; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h index a57919478d3b..4b55d0d9703a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h @@ -34,6 +34,7 @@ #define AMDGPU_NUM_VMID 16 struct amdgpu_device; +struct amdgpu_fpriv; struct amdgpu_vm; struct amdgpu_ring; struct amdgpu_sync; @@ -70,7 +71,10 @@ struct amdgpu_vmid_mgr { bool reserved_vmid; }; -int amdgpu_pasid_alloc(unsigned int bits); +int amdgpu_pasid_alloc(unsigned int bits, struct amdgpu_fpriv *fpriv); +void amdgpu_pasid_lock(unsigned long *flags); +void amdgpu_pasid_unlock(unsigned long flags); +struct amdgpu_fpriv *amdgpu_pasid_get_fpriv_locked(u32 pasid); void amdgpu_pasid_free(u32 pasid); void amdgpu_pasid_free_delayed(struct dma_resv *resv, u32 pasid); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 287ede40cd91..b2e2e3533c69 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -1488,7 +1488,7 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) goto out_suspend; } - pasid = amdgpu_pasid_alloc(16); + pasid = amdgpu_pasid_alloc(16, NULL); if (pasid < 0) { dev_warn(adev->dev, "No more PASIDs available!"); pasid = 0; -- cgit v1.2.3 From b0ae60ea3f3f17809214df593aba4050b089e9e1 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Sun, 19 Jul 2026 21:39:02 +0530 Subject: drm/amdgpu: Resolve VM through DRM PASID ownership MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allocate DRM PASIDs with fpriv and resolve VM lookup users through: PASID -> fpriv -> VM This preserves the root BO reference and revalidation flow in amdgpu_vm_lock_by_pasid(). The obsolete per-device vm_manager.pasids mapping is removed by the following patch in this series. v6: - Allocate and publish the DRM PASID after amdgpu_vm_init() completes. - Assign the allocated PASID to vm->pasid in the DRM open path. - Unpublish PASID ownership before tearing down the VM in both the open-failure and normal file-close paths. v5: - Delay DRM PASID allocation until after amdgpu_vm_init() completes. v4: - Allocate DRM PASIDs with fpriv directly. - Squash ownership registration and PASID lookup conversion. Cc: Alex Deucher Suggested-by: Christian König Signed-off-by: Srinivasan Shanmugam Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 32 ++++++++++++++------------ drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 40 +++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index b2e2e3533c69..46eabdda49f3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -1376,11 +1376,11 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) memset(&gpuvm_fault, 0, sizeof(gpuvm_fault)); - xa_lock_irqsave(&adev->vm_manager.pasids, flags); + amdgpu_pasid_lock(&flags); gpuvm_fault.addr = vm->fault_info.addr; gpuvm_fault.status = vm->fault_info.status; gpuvm_fault.vmhub = vm->fault_info.vmhub; - xa_unlock_irqrestore(&adev->vm_manager.pasids, flags); + amdgpu_pasid_unlock(flags); return copy_to_user(out, &gpuvm_fault, min((size_t)size, sizeof(gpuvm_fault))) ? -EFAULT : 0; @@ -1465,7 +1465,7 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) struct amdgpu_device *adev = drm_to_adev(dev); struct amdgpu_fpriv *fpriv; struct drm_exec exec; - int r, pasid; + int r, pasid = 0; /* Ensure IB tests are run on ring */ flush_delayed_work(&adev->delayed_init_work); @@ -1488,22 +1488,23 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) goto out_suspend; } - pasid = amdgpu_pasid_alloc(16, NULL); - if (pasid < 0) { - dev_warn(adev->dev, "No more PASIDs available!"); - pasid = 0; - } - r = amdgpu_xcp_open_device(adev, fpriv, file_priv); if (r) goto error_pasid; amdgpu_debugfs_vm_init(file_priv); - r = amdgpu_vm_init(adev, &fpriv->vm, fpriv->xcp_id, pasid); + r = amdgpu_vm_init(adev, &fpriv->vm, fpriv->xcp_id, 0); if (r) goto error_pasid; + pasid = amdgpu_pasid_alloc(16, fpriv); + if (pasid < 0) { + dev_warn(adev->dev, "No more PASIDs available!"); + pasid = 0; + } + fpriv->vm.pasid = pasid; + drm_exec_init(&exec, DRM_EXEC_IGNORE_DUPLICATES, 0); drm_exec_until_all_locked(&exec) { r = amdgpu_vm_lock_pd(&fpriv->vm, &exec, 0); @@ -1547,12 +1548,14 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) goto out_suspend; error_vm: + if (pasid) { + amdgpu_pasid_free(pasid); + pasid = 0; + } + amdgpu_vm_fini(adev, &fpriv->vm); error_pasid: - if (pasid) - amdgpu_pasid_free(pasid); - kfree(fpriv); out_suspend: @@ -1608,10 +1611,11 @@ void amdgpu_driver_postclose_kms(struct drm_device *dev, } amdgpu_ctx_mgr_fini(&fpriv->ctx_mgr); - amdgpu_vm_fini(adev, &fpriv->vm); if (pasid) amdgpu_pasid_free_delayed(pd->tbo.base.resv, pasid); + + amdgpu_vm_fini(adev, &fpriv->vm); amdgpu_bo_unref(&pd); xa_for_each(&fpriv->bo_list_handles, handle, list) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index eb6a70fe79ce..146902ffc875 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2506,14 +2506,16 @@ amdgpu_vm_get_task_info_vm(struct amdgpu_vm *vm) struct amdgpu_task_info * amdgpu_vm_get_task_info_pasid(struct amdgpu_device *adev, u32 pasid) { + struct amdgpu_fpriv *fpriv; struct amdgpu_task_info *ti; struct amdgpu_vm *vm; unsigned long flags; - xa_lock_irqsave(&adev->vm_manager.pasids, flags); - vm = xa_load(&adev->vm_manager.pasids, pasid); + amdgpu_pasid_lock(&flags); + fpriv = amdgpu_pasid_get_fpriv_locked(pasid); + vm = fpriv ? &fpriv->vm : NULL; ti = amdgpu_vm_get_task_info_vm(vm); - xa_unlock_irqrestore(&adev->vm_manager.pasids, flags); + amdgpu_pasid_unlock(flags); return ti; } @@ -2935,14 +2937,16 @@ struct amdgpu_vm *amdgpu_vm_lock_by_pasid(struct amdgpu_device *adev, u32 pasid, struct drm_exec *exec) { unsigned long irqflags; + struct amdgpu_fpriv *fpriv; struct amdgpu_bo *root; struct amdgpu_vm *vm; int r; - xa_lock_irqsave(&adev->vm_manager.pasids, irqflags); - vm = xa_load(&adev->vm_manager.pasids, pasid); - root = vm ? amdgpu_bo_ref(vm->root.bo) : NULL; - xa_unlock_irqrestore(&adev->vm_manager.pasids, irqflags); + amdgpu_pasid_lock(&irqflags); + fpriv = amdgpu_pasid_get_fpriv_locked(pasid); + vm = fpriv ? &fpriv->vm : NULL; + root = vm && vm->root.bo ? amdgpu_bo_ref(vm->root.bo) : NULL; + amdgpu_pasid_unlock(irqflags); if (!root) return NULL; @@ -2954,11 +2958,17 @@ struct amdgpu_vm *amdgpu_vm_lock_by_pasid(struct amdgpu_device *adev, } /* Double check that the VM still exists */ - xa_lock_irqsave(&adev->vm_manager.pasids, irqflags); - vm = xa_load(&adev->vm_manager.pasids, pasid); - if (vm && vm->root.bo != root) + amdgpu_pasid_lock(&irqflags); + fpriv = amdgpu_pasid_get_fpriv_locked(pasid); + if (!fpriv) { vm = NULL; - xa_unlock_irqrestore(&adev->vm_manager.pasids, irqflags); + } else { + vm = &fpriv->vm; + if (vm->root.bo != root) + vm = NULL; + } + amdgpu_pasid_unlock(irqflags); + if (!vm) { drm_exec_unlock_obj(exec, &root->tbo.base); amdgpu_bo_unref(&root); @@ -3155,12 +3165,14 @@ void amdgpu_vm_update_fault_cache(struct amdgpu_device *adev, uint32_t status, unsigned int vmhub) { + struct amdgpu_fpriv *fpriv; struct amdgpu_vm *vm; unsigned long flags; - xa_lock_irqsave(&adev->vm_manager.pasids, flags); + amdgpu_pasid_lock(&flags); - vm = xa_load(&adev->vm_manager.pasids, pasid); + fpriv = amdgpu_pasid_get_fpriv_locked(pasid); + vm = fpriv ? &fpriv->vm : NULL; /* Don't update the fault cache if status is 0. In the multiple * fault case, subsequent faults will return a 0 status which is * useless for userspace and replaces the useful fault status, so @@ -3193,7 +3205,7 @@ void amdgpu_vm_update_fault_cache(struct amdgpu_device *adev, WARN_ONCE(1, "Invalid vmhub %u\n", vmhub); } } - xa_unlock_irqrestore(&adev->vm_manager.pasids, flags); + amdgpu_pasid_unlock(flags); } void amdgpu_vm_print_task_info(struct amdgpu_device *adev, -- cgit v1.2.3 From 7f33fb4f4f159450d5136dc3e111568bc65e2afd Mon Sep 17 00:00:00 2001 From: Dillon Varone Date: Thu, 26 Mar 2026 13:10:10 -0400 Subject: drm/amd/display: Correct pipe usage for populating stream config [WHY&HOW] Was incorrectly using stream index to index pipes, when should have been using callback to get otg master pipe for stream. Acked-by: Tom Chung Signed-off-by: Dillon Varone Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../dc/dml2_0/dml21/dml21_translation_helper.c | 28 ++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c index c1a3e2496983..51260369cd8a 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c @@ -90,7 +90,7 @@ static unsigned int calc_max_hardware_v_total(const struct dc_stream_state *stre static void populate_dml21_timing_config_from_stream_state(struct dml2_timing_cfg *timing, struct dc_stream_state *stream, - struct pipe_ctx *pipe_ctx, + struct pipe_ctx *otg_master_pipe, struct dml2_context *dml_ctx) { const unsigned int min_v_front_porch = (stream->timing.flags.INTERLACE != 0) ? 2 : 1; @@ -99,24 +99,24 @@ static void populate_dml21_timing_config_from_stream_state(struct dml2_timing_cf uint64_t min_hardware_refresh_in_uhz; uint32_t pix_clk_100hz; - timing->h_active = stream->timing.h_addressable + stream->timing.h_border_left + stream->timing.h_border_right + pipe_ctx->dsc_padding_params.dsc_hactive_padding; + timing->h_active = stream->timing.h_addressable + stream->timing.h_border_left + stream->timing.h_border_right + otg_master_pipe->dsc_padding_params.dsc_hactive_padding; timing->v_active = stream->timing.v_addressable + stream->timing.v_border_bottom + stream->timing.v_border_top; timing->h_front_porch = stream->timing.h_front_porch; timing->v_front_porch = stream->timing.v_front_porch > min_v_front_porch ? stream->timing.v_front_porch : min_v_front_porch; timing->pixel_clock_khz = stream->timing.pix_clk_100hz / 10; - if (pipe_ctx->dsc_padding_params.dsc_hactive_padding != 0) - timing->pixel_clock_khz = pipe_ctx->dsc_padding_params.dsc_pix_clk_100hz / 10; + if (otg_master_pipe->dsc_padding_params.dsc_hactive_padding != 0) + timing->pixel_clock_khz = otg_master_pipe->dsc_padding_params.dsc_pix_clk_100hz / 10; if (stream->timing.timing_3d_format == TIMING_3D_FORMAT_HW_FRAME_PACKING) timing->pixel_clock_khz *= 2; - timing->h_total = stream->timing.h_total + pipe_ctx->dsc_padding_params.dsc_htotal_padding; + timing->h_total = stream->timing.h_total + otg_master_pipe->dsc_padding_params.dsc_htotal_padding; timing->v_total = stream->timing.v_total; timing->h_sync_width = stream->timing.h_sync_width; timing->interlaced = (stream->timing.flags.INTERLACE != 0); hblank_start = stream->timing.h_total - stream->timing.h_front_porch; - timing->h_blank_end = hblank_start - stream->timing.h_addressable - pipe_ctx->dsc_padding_params.dsc_hactive_padding + timing->h_blank_end = hblank_start - stream->timing.h_addressable - otg_master_pipe->dsc_padding_params.dsc_hactive_padding - stream->timing.h_border_left - stream->timing.h_border_right; if (hblank_start < stream->timing.h_addressable) @@ -135,8 +135,8 @@ static void populate_dml21_timing_config_from_stream_state(struct dml2_timing_cf /* limit min refresh rate to DC cap */ min_hardware_refresh_in_uhz = stream->timing.min_refresh_in_uhz; if (stream->ctx->dc->caps.max_v_total != 0) { - if (pipe_ctx->dsc_padding_params.dsc_hactive_padding != 0) { - pix_clk_100hz = pipe_ctx->dsc_padding_params.dsc_pix_clk_100hz; + if (otg_master_pipe->dsc_padding_params.dsc_hactive_padding != 0) { + pix_clk_100hz = otg_master_pipe->dsc_padding_params.dsc_pix_clk_100hz; } else { pix_clk_100hz = stream->timing.pix_clk_100hz; } @@ -197,7 +197,7 @@ static void populate_dml21_timing_config_from_stream_state(struct dml2_timing_cf } static void populate_dml21_output_config_from_stream_state(struct dml2_link_output_cfg *output, - struct dc_stream_state *stream, const struct pipe_ctx *pipe) + struct dc_stream_state *stream, const struct pipe_ctx *otg_master_pipe) { output->output_dp_lane_count = 4; @@ -205,7 +205,7 @@ static void populate_dml21_output_config_from_stream_state(struct dml2_link_outp case SIGNAL_TYPE_DISPLAY_PORT_MST: case SIGNAL_TYPE_DISPLAY_PORT: output->output_encoder = dml2_dp; - if (check_dp2p0_output_encoder(pipe)) + if (check_dp2p0_output_encoder(otg_master_pipe)) output->output_encoder = dml2_dp2p0; break; case SIGNAL_TYPE_EDP: @@ -795,6 +795,7 @@ bool dml21_map_dc_state_into_dml_display_cfg(const struct dc *in_dc, struct dc_s int disp_cfg_stream_location, disp_cfg_plane_location; struct dml2_display_cfg *dml_dispcfg = &dml_ctx->v21.display_config; unsigned int plane_count = 0; + struct pipe_ctx *otg_master_pipe; memset(&dml_ctx->v21.dml_to_dc_pipe_mapping, 0, sizeof(struct dml2_dml_to_dc_pipe_mapping)); @@ -819,9 +820,12 @@ bool dml21_map_dc_state_into_dml_display_cfg(const struct dc *in_dc, struct dc_s if (disp_cfg_stream_location < 0) disp_cfg_stream_location = dml_dispcfg->num_streams++; + otg_master_pipe = dml_ctx->config.callbacks.get_otg_master_for_stream(&context->res_ctx, context->streams[stream_index]); + ASSERT(otg_master_pipe); + ASSERT(disp_cfg_stream_location >= 0 && disp_cfg_stream_location < __DML2_WRAPPER_MAX_STREAMS_PLANES__); - populate_dml21_timing_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].timing, context->streams[stream_index], &context->res_ctx.pipe_ctx[stream_index], dml_ctx); - populate_dml21_output_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].output, context->streams[stream_index], &context->res_ctx.pipe_ctx[stream_index]); + populate_dml21_timing_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].timing, context->streams[stream_index], otg_master_pipe, dml_ctx); + populate_dml21_output_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].output, context->streams[stream_index], otg_master_pipe); populate_dml21_stream_overrides_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location], context->streams[stream_index], &context->stream_status[stream_index]); dml_dispcfg->stream_descriptors[disp_cfg_stream_location].overrides.hw.twait_budgeting.fclk_pstate = dml2_twait_budgeting_setting_if_needed; -- cgit v1.2.3 From 17ba75a0db653b10f9bdfe58673bad0c37b3cd21 Mon Sep 17 00:00:00 2001 From: Austin Zheng Date: Wed, 3 Jun 2026 16:38:51 -0400 Subject: drm/amd/display: Add Writeback Watermarks and Latency Fields Add fields that can used for writeback watermarks and latency margin Signed-off-by: Austin Zheng Reviewed-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h | 1 + .../amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 3 +++ .../amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h | 2 ++ 3 files changed, 6 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h index 8d7960a340c2..cdfb5cd09edb 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h @@ -457,6 +457,7 @@ struct dml2_display_cfg_programming { double fclk_pstate_change_us; double usr_retraining_us; double temp_read_or_ppt_watermark_us; + double writeback_temp_read_or_ppt_watermark_us; } watermarks; struct { diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c index 51a66e1be7a1..b667fc9ad75f 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c @@ -317,6 +317,7 @@ dml_get_var_func(meta_trip_memory_us, double, mode_lib->mp.MetaTripToMemory); dml_get_var_func(wm_fclk_change, double, mode_lib->mp.Watermark.FCLKChangeWatermark); dml_get_var_func(wm_usr_retraining, double, mode_lib->mp.Watermark.USRRetrainingWatermark); dml_get_var_func(wm_temp_read_or_ppt, double, mode_lib->mp.Watermark.temp_read_or_ppt_watermark_us); +dml_get_var_func(wm_writeback_temp_read_or_ppt, double, mode_lib->mp.Watermark.writeback_temp_read_or_ppt_watermark_us); dml_get_var_func(wm_dram_clock_change, double, mode_lib->mp.Watermark.DRAMClockChangeWatermark); dml_get_var_func(fraction_of_urgent_bandwidth, double, mode_lib->mp.FractionOfUrgentBandwidth); dml_get_var_func(fraction_of_urgent_bandwidth_imm_flip, double, mode_lib->mp.FractionOfUrgentBandwidthImmediateFlip); @@ -13230,6 +13231,8 @@ void dml2_core_calcs_get_informative(const struct dml2_core_internal_display_mod out->informative.watermarks.fclk_pstate_change_us = dml_get_wm_fclk_change(mode_lib); out->informative.watermarks.usr_retraining_us = dml_get_wm_usr_retraining(mode_lib); out->informative.watermarks.temp_read_or_ppt_watermark_us = dml_get_wm_temp_read_or_ppt(mode_lib); + out->informative.watermarks.writeback_temp_read_or_ppt_watermark_us = dml_get_wm_writeback_temp_read_or_ppt(mode_lib); + out->informative.mall.total_surface_size_in_mall_bytes = 0; out->informative.dpp.total_num_dpps_required = 0; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h index e9f970794488..131cec64aa48 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h @@ -212,6 +212,7 @@ struct dml2_core_internal_watermarks { double Z8StutterEnterPlusExitWatermark; double USRRetrainingWatermark; double temp_read_or_ppt_watermark_us; + double writeback_temp_read_or_ppt_watermark_us; }; struct dml2_core_internal_mode_support_info { @@ -1269,6 +1270,7 @@ struct dml2_core_calcs_CalculateWatermarksMALLUseAndDRAMSpeedChangeSupport_local double FullDETBufferingTimeC; double WritebackDRAMClockChangeLatencyMargin; double WritebackFCLKChangeLatencyMargin; + double WritebackTempReadOrPptLatencyMargin; double WritebackLatencyHiding; unsigned int TotalActiveWriteback; -- cgit v1.2.3 From 68e188d91a24fed127bbcf186dbd60056ca18d86 Mon Sep 17 00:00:00 2001 From: Dillon Varone Date: Tue, 16 Jun 2026 18:21:57 -0400 Subject: drm/amd/display: Add MCIF ARB programming structures [WHY&HOW] Adds required structures to configure MCIF ARB for DWB. Signed-off-by: Dillon Varone Reviewed-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../dc/dml2_0/dml21/inc/dml_top_dchub_registers.h | 20 ++++++++++++++++++++ .../amd/display/dc/dml2_0/dml21/inc/dml_top_types.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h index bf57df42d1d9..5669be0a7340 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h @@ -153,6 +153,13 @@ struct dml2_dchub_per_pipe_register_set { uint32_t det_size; }; +struct dml2_mcif_per_pipe_register_set { + unsigned int time_per_pixel; // U6.6 format + unsigned int arbitration_slice; + unsigned int slice_lines; + unsigned int max_scaled_time_ns; +}; + struct dml2_dchub_watermark_regs { /* watermarks */ uint32_t urgent; @@ -188,4 +195,17 @@ struct dml2_dchub_global_register_set { unsigned int num_watermark_sets; }; +struct dml2_mcif_watermark_regs { + /* watermarks */ + uint32_t urgent; /* (CLI) */ + uint32_t uclk_pstate; + uint32_t fclk_pstate; + uint32_t temp_read_or_ppt; +}; + +struct dml2_mcif_global_register_set { + struct dml2_mcif_watermark_regs wm_regs[DML2_DCHUB_WATERMARK_SET_NUM]; + unsigned int num_watermark_sets; +}; + #endif diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h index cdfb5cd09edb..bd0d7549d20f 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h @@ -436,12 +436,14 @@ struct dml2_display_cfg_programming { } z8_stutter; struct dml2_dchub_global_register_set global_regs; + struct dml2_mcif_global_register_set mcif_global_regs; struct dml2_per_plane_programming plane_programming[DML2_MAX_PLANES]; struct dml2_per_stream_programming stream_programming[DML2_MAX_PLANES]; // Don't access this structure directly, access it through plane_programming.pipe_regs struct dml2_dchub_per_pipe_register_set pipe_regs[DML2_MAX_PLANES]; + struct dml2_mcif_per_pipe_register_set mcif_regs[DML2_MAX_WRITEBACK]; struct { struct { -- cgit v1.2.3 From 0c0d5174b09640d8b560764aa5a177630e076e93 Mon Sep 17 00:00:00 2001 From: Ray Wu Date: Fri, 3 Jul 2026 09:14:49 +0800 Subject: drm/amd/display: Increase HDMI AV mute wait from 2 to 3 frames Some HDMI sinks need additional GCP packets to properly process the mute state before the timing generator is disabled, especially after link re-establishment with HDMI 2.0 scrambling enabled. Waiting for only 2 frames is insufficient for certain monitor firmware, resulting in garbled display output on resume from suspend. Increase the AV mute wait in dcn30_set_avmute() from 2 to 3 frames to ensure the sink receives enough GCP packets. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5167 Assisted-by: Cursor:Claude-Opus-4.6 Reviewed-by: Wayne Lin Signed-off-by: Ray Wu Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c index aed9d06ec538..aa7707b2b25b 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c @@ -849,13 +849,19 @@ void dcn30_set_avmute(struct pipe_ctx *pipe_ctx, bool enable) pipe_ctx->stream_res.stream_enc, enable); - /* Wait for two frame to make sure AV mute is sent out */ + /* Wait for three frames to make sure AV mute is sent out. + * Some HDMI sinks need additional GCP packets to properly + * process the mute state, especially after link re-establishment + * with HDMI 2.0 scrambling enabled. + */ if (enable && pipe_ctx->stream_res.tg->funcs->is_tg_enabled(pipe_ctx->stream_res.tg)) { + int i; + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); - pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VBLANK); - pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); - pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VBLANK); - pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); + for (i = 0; i < 3; i++) { + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VBLANK); + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); + } } } } -- cgit v1.2.3 From f1d518d1b799826fdb9bdedbbc557aa85a2cc6d3 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 09:16:18 -0600 Subject: drm/amd/display: add dm_dmub_hw_init KUnit coverage [WHAT] Add KUnit coverage for dm_dmub_hw_init() beyond the existing early-return cases. Introduce reusable fake-DMUB fixtures (fake dmub_srv/firmware, DMCU/ABM stubs, and adev builders) so the init path runs without real register access, TTM allocation, or firmware loading. New cases cover the fake-DMUB success path, unsupported hardware, BSS data copy, hardware-init failure, auto-load timeout, the APU/DPIA DCN3.5 params, the DCN3.1.x sanity-check ranges, and DMCU/ABM initialization. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 315 +++++++++++++++++++++ 1 file changed, 315 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c index bf90ccfbf431..4c01f7919170 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c @@ -6,14 +6,20 @@ */ #include +#include #include "dc.h" #include "dc/inc/core_types.h" +#include "dc/inc/hw/dmcu.h" +#include "dc/inc/hw/abm.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" +#include "dm_services.h" #include "dmub/dmub_srv.h" #include "amdgpu_dm_dmub.h" +#define DM_TEST_FW_SIZE 512 + /* Tests for dm_register_dmub_notify_callback() */ static void dummy_callback(struct amdgpu_device *adev, @@ -21,6 +27,99 @@ static void dummy_callback(struct amdgpu_device *adev, { } +static bool dm_test_dmub_supported(struct dmub_srv *dmub) +{ + return true; +} + +static bool dm_test_dmub_unsupported(struct dmub_srv *dmub) +{ + return false; +} + +static bool dm_test_dmub_hw_initialized(struct dmub_srv *dmub) +{ + return true; +} + +static union dmub_fw_boot_status dm_test_dmub_fw_ready(struct dmub_srv *dmub) +{ + union dmub_fw_boot_status status = { 0 }; + + status.bits.dal_fw = 1; + status.bits.mailbox_rdy = 1; + return status; +} + +static union dmub_fw_boot_status dm_test_dmub_fw_not_ready(struct dmub_srv *dmub) +{ + union dmub_fw_boot_status status = { 0 }; + + return status; +} + +static void dm_test_dmub_init_reg_offsets(struct dmub_srv *dmub, + struct dc_context *ctx) +{ +} + +static bool dm_test_dmcu_init(struct dmcu *dmcu) +{ + return true; +} + +static bool dm_test_dmcu_is_initialized(struct dmcu *dmcu) +{ + return true; +} + +static const struct dmcu_funcs dm_test_dmcu_funcs = { + .dmcu_init = dm_test_dmcu_init, + .is_dmcu_initialized = dm_test_dmcu_is_initialized, +}; + +static struct dmub_srv *dm_test_alloc_dmub_srv(struct kunit *test) +{ + struct dmub_srv *dmub_srv; + + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv); + + dmub_srv->sw_init = true; + dmub_srv->hw_init = true; + dmub_srv->power_state = DMUB_POWER_STATE_D0; + dmub_srv->hw_funcs.is_supported = dm_test_dmub_supported; + dmub_srv->hw_funcs.is_hw_init = dm_test_dmub_hw_initialized; + dmub_srv->hw_funcs.get_fw_status = dm_test_dmub_fw_ready; + dmub_srv->hw_funcs.init_reg_offsets = dm_test_dmub_init_reg_offsets; + + return dmub_srv; +} + +static const struct firmware *dm_test_alloc_dmub_fw(struct kunit *test) +{ + struct dmcub_firmware_header_v1_0 *hdr; + struct firmware *fw; + u8 *data; + + fw = kunit_kzalloc(test, sizeof(*fw), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fw); + + data = kunit_kzalloc(test, DM_TEST_FW_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, data); + + hdr = (struct dmcub_firmware_header_v1_0 *)data; + hdr->header.ucode_array_offset_bytes = cpu_to_le32(0); + hdr->header.ucode_version = cpu_to_le32(DMUB_FW_VERSION(9, 9, 9)); + hdr->inst_const_bytes = cpu_to_le32(PSP_HEADER_BYTES_256); + hdr->bss_data_bytes = cpu_to_le32(0); + + fw->size = DM_TEST_FW_SIZE; + fw->data = data; + + return fw; +} + /** * dm_test_register_dmub_notify_callback_null_callback - Test null callback is rejected * @test: The KUnit test context @@ -395,6 +494,7 @@ static void dm_test_get_default_ips_mode_newer_default(struct kunit *test) static struct amdgpu_device *dm_test_alloc_adev_with_dc(struct kunit *test) { struct amdgpu_device *adev; + struct dc_context *ctx; struct dc *dc; struct resource_pool *res_pool; @@ -407,12 +507,47 @@ static struct amdgpu_device *dm_test_alloc_adev_with_dc(struct kunit *test) res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + dc->res_pool = res_pool; + dc->ctx = ctx; + ctx->dc = dc; + ctx->driver_context = adev; adev->dm.dc = dc; return adev; } +static struct amdgpu_device *dm_test_alloc_adev_with_dmub(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_srv_fb_info *fb_info; + int i; + + adev = dm_test_alloc_adev_with_dc(test); + + fb_info = kunit_kzalloc(test, sizeof(*fb_info), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb_info); + + fb_info->num_fb = DMUB_WINDOW_TOTAL; + for (i = 0; i < DMUB_WINDOW_TOTAL; i++) { + fb_info->fb[i].size = PAGE_SIZE; + fb_info->fb[i].cpu_addr = kunit_kzalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb_info->fb[i].cpu_addr); + } + + adev->dm.dmub_srv = dm_test_alloc_dmub_srv(test); + adev->dm.dmub_fb_info = fb_info; + adev->dm.dmub_fw = dm_test_alloc_dmub_fw(test); + adev->bios = kunit_kzalloc(test, 4, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev->bios); + adev->bios_size = 4; + adev->dm.fw_inst_size = 0; + + return adev; +} + /** * dm_test_dmub_hw_init_no_dmub_srv - Test hw init returns 0 when DMUB unsupported * @test: The KUnit test context @@ -476,6 +611,177 @@ static void dm_test_dmub_hw_init_no_firmware(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), -EINVAL); } +/** + * dm_test_dmub_hw_init_success_fake_dmub - Test hw init with a fake DMUB service + * @test: The KUnit test context + * + * With fake DMUB callbacks and preallocated framebuffer windows, the init path + * should reach DMUB service initialization without real register access. + */ +static void dm_test_dmub_hw_init_success_fake_dmub(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->dm.dmub_srv->hw_init); + KUNIT_EXPECT_NOT_NULL(test, adev->dm.dc->ctx->dmub_srv); +} + +/** + * dm_test_dmub_hw_init_no_hw_support - Test hw init returns 0 when HW is unsupported + * @test: The KUnit test context + * + * When the DMUB service reports no hardware support, dm_dmub_hw_init() should + * log and return 0 without initializing the DMUB hardware. + */ +static void dm_test_dmub_hw_init_no_hw_support(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->dm.dmub_srv->hw_funcs.is_supported = dm_test_dmub_unsupported; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_NULL(test, adev->dm.dc->ctx->dmub_srv); +} + +/** + * dm_test_dmub_hw_init_bss_data - Test hw init copies BSS data into FB memory + * @test: The KUnit test context + * + * When the DMUB firmware declares a non-zero BSS data size, dm_dmub_hw_init() + * should copy that region into the BSS framebuffer window. + */ +static void dm_test_dmub_hw_init_bss_data(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + struct dmcub_firmware_header_v1_0 *hdr; + + hdr = (struct dmcub_firmware_header_v1_0 *)adev->dm.dmub_fw->data; + hdr->bss_data_bytes = cpu_to_le32(16); + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->dm.dmub_srv->hw_init); +} + +/** + * dm_test_dmub_hw_init_hw_init_fails - Test hw init returns -EINVAL on DMUB init failure + * @test: The KUnit test context + * + * A framebuffer-info window count below the required total makes + * dmub_srv_hw_init() reject the request, so dm_dmub_hw_init() logs and + * returns -EINVAL. (The rejection path emits a one-time WARN via ASSERT.) + */ +static void dm_test_dmub_hw_init_hw_init_fails(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->dm.dmub_fb_info->num_fb = 0; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), -EINVAL); +} + +/** + * dm_test_dmub_hw_init_auto_load_timeout - Test hw init tolerates an auto-load timeout + * @test: The KUnit test context + * + * When the DMUB firmware never reports ready, dmub_srv_wait_for_auto_load() + * times out; dm_dmub_hw_init() only warns and still completes successfully. + */ +static void dm_test_dmub_hw_init_auto_load_timeout(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->dm.dmub_srv->hw_funcs.get_fw_status = dm_test_dmub_fw_not_ready; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_NOT_NULL(test, adev->dm.dc->ctx->dmub_srv); +} + +/** + * dm_test_dmub_hw_init_apu_dpia_dcn35 - Test hw init APU DPIA and DCN35 hw params + * @test: The KUnit test context + * + * On a DCN3.5 APU with a USB4 DPIA link, dm_dmub_hw_init() should populate the + * DPIA hw params and the DCN3.5 IPS-sequential hw params before initializing + * the fake DMUB service. + */ +static void dm_test_dmub_hw_init_apu_dpia_dcn35(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 0); + adev->dm.dc->caps.is_apu = true; + adev->dm.dc->res_pool->usb4_dpia_count = 1; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->dm.dmub_srv->hw_init); +} + +/** + * dm_test_dmub_hw_init_sanity_checks_dcn31 - Test hw init enables DCN31 sanity checks + * @test: The KUnit test context + * + * On DCN3.1.2 with a DMCUB firmware version in the affected range, + * dm_dmub_hw_init() should enable the DC sanity-check debug flag. + */ +static void dm_test_dmub_hw_init_sanity_checks_dcn31(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 2); + adev->dm.dmcub_fw_version = DMUB_FW_VERSION(4, 0, 10); + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->dm.dc->debug.sanity_checks); +} + +/** + * dm_test_dmub_hw_init_sanity_checks_dcn314 - Test hw init enables DCN314 sanity checks + * @test: The KUnit test context + * + * On DCN3.1.4 with a DMCUB firmware version in the affected range, + * dm_dmub_hw_init() should enable the DC sanity-check debug flag. + */ +static void dm_test_dmub_hw_init_sanity_checks_dcn314(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + adev->dm.dmcub_fw_version = DMUB_FW_VERSION(4, 0, 10); + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, adev->dm.dc->debug.sanity_checks); +} + +/** + * dm_test_dmub_hw_init_dmcu_abm - Test hw init initializes DMCU and ABM when present + * @test: The KUnit test context + * + * When the resource pool exposes a DMCU and ABM, dm_dmub_hw_init() should + * program the PSP version, invoke the DMCU init callback, and record the + * running state reported by the DMCU. + */ +static void dm_test_dmub_hw_init_dmcu_abm(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + struct dmcu *dmcu; + struct abm *abm; + + dmcu = kunit_kzalloc(test, sizeof(*dmcu), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmcu); + + abm = kunit_kzalloc(test, sizeof(*abm), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, abm); + + dmcu->funcs = &dm_test_dmcu_funcs; + dmcu->psp_version = 0x12345678; + adev->dm.dc->res_pool->dmcu = dmcu; + adev->dm.dc->res_pool->abm = abm; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); + KUNIT_EXPECT_TRUE(test, abm->dmcu_is_running); +} + /* Tests for dm_dmub_hw_resume() */ /** @@ -561,6 +867,15 @@ static struct kunit_case amdgpu_dm_dmub_tests[] = { KUNIT_CASE(dm_test_dmub_hw_init_no_dmub_srv), KUNIT_CASE(dm_test_dmub_hw_init_no_fb_info), KUNIT_CASE(dm_test_dmub_hw_init_no_firmware), + KUNIT_CASE(dm_test_dmub_hw_init_success_fake_dmub), + KUNIT_CASE(dm_test_dmub_hw_init_no_hw_support), + KUNIT_CASE(dm_test_dmub_hw_init_bss_data), + KUNIT_CASE(dm_test_dmub_hw_init_hw_init_fails), + KUNIT_CASE(dm_test_dmub_hw_init_auto_load_timeout), + KUNIT_CASE(dm_test_dmub_hw_init_apu_dpia_dcn35), + KUNIT_CASE(dm_test_dmub_hw_init_sanity_checks_dcn31), + KUNIT_CASE(dm_test_dmub_hw_init_sanity_checks_dcn314), + KUNIT_CASE(dm_test_dmub_hw_init_dmcu_abm), /* dm_dmub_hw_resume() */ KUNIT_CASE(dm_test_dmub_hw_resume_no_dmub_srv), /* dm_dmub_sw_init() */ -- cgit v1.2.3 From b2e286091aba833c5e09a7ceb5eebcc08ced2695 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 09:17:33 -0600 Subject: drm/amd/display: add dm_dmub_hw_resume KUnit coverage [WHAT] Extend KUnit coverage for dm_dmub_hw_resume() using the fake-DMUB fixtures. New cases cover the already-initialized wait path, the full reinitialization path, a failed init-state query, and an auto-load timeout, none of which require real hardware. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c index 4c01f7919170..bae34436c89e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c @@ -801,6 +801,79 @@ static void dm_test_dmub_hw_resume_no_dmub_srv(struct kunit *test) dm_dmub_hw_resume(adev); } +/** + * dm_test_dmub_hw_resume_initialized_dmub - Test resume waits for initialized DMUB + * @test: The KUnit test context + * + * When the fake DMUB service reports hardware already initialized, resume + * should only wait for firmware readiness and skip full reinitialization. + */ +static void dm_test_dmub_hw_resume_initialized_dmub(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + + adev->dm.dmub_srv = dm_test_alloc_dmub_srv(test); + + /* Must not crash. */ + dm_dmub_hw_resume(adev); +} + +/** + * dm_test_dmub_hw_resume_full_init - Test resume performs full init when uninitialized + * @test: The KUnit test context + * + * When the fake DMUB service reports hardware not yet initialized, resume + * should continue into a full dm_dmub_hw_init() and create the DC DMUB + * server. + */ +static void dm_test_dmub_hw_resume_full_init(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->dm.dmub_srv->hw_init = false; + + dm_dmub_hw_resume(adev); + + KUNIT_EXPECT_NOT_NULL(test, adev->dm.dc->ctx->dmub_srv); +} + +/** + * dm_test_dmub_hw_resume_init_check_failed - Test resume handles a failed init check + * @test: The KUnit test context + * + * When the DMUB service is not software-initialized, the init-state query + * fails and resume continues into dm_dmub_hw_init(), which also fails; the + * call must warn and return without crashing. + */ +static void dm_test_dmub_hw_resume_init_check_failed(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dmub(test); + + adev->dm.dmub_srv->sw_init = false; + + /* Must not crash. */ + dm_dmub_hw_resume(adev); +} + +/** + * dm_test_dmub_hw_resume_auto_load_timeout - Test resume tolerates an auto-load timeout + * @test: The KUnit test context + * + * When the DMUB reports hardware already initialized but the firmware never + * signals ready, resume's auto-load wait times out and only warns; the call + * must not crash. + */ +static void dm_test_dmub_hw_resume_auto_load_timeout(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + + adev->dm.dmub_srv = dm_test_alloc_dmub_srv(test); + adev->dm.dmub_srv->hw_funcs.get_fw_status = dm_test_dmub_fw_not_ready; + + /* Must not crash; auto-load times out and only warns. */ + dm_dmub_hw_resume(adev); +} + /* Tests for dm_dmub_sw_init() */ /** @@ -878,6 +951,10 @@ static struct kunit_case amdgpu_dm_dmub_tests[] = { KUNIT_CASE(dm_test_dmub_hw_init_dmcu_abm), /* dm_dmub_hw_resume() */ KUNIT_CASE(dm_test_dmub_hw_resume_no_dmub_srv), + KUNIT_CASE(dm_test_dmub_hw_resume_initialized_dmub), + KUNIT_CASE(dm_test_dmub_hw_resume_full_init), + KUNIT_CASE(dm_test_dmub_hw_resume_init_check_failed), + KUNIT_CASE(dm_test_dmub_hw_resume_auto_load_timeout), /* dm_dmub_sw_init() */ KUNIT_CASE(dm_test_dmub_sw_init_unsupported_asic), /* dm_init_microcode() */ -- cgit v1.2.3 From f8e0a84ab1b786fda9a50087c267de67ec5aa79c Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 09:21:06 -0600 Subject: drm/amd/display: add fused IO KUnit coverage [WHAT] Add KUnit coverage for the DMUB fused IO helpers: the dm_dmub_aux_fused_io_callback() NULL-argument guard and the abort_fused_io() no-DMUB-service path. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 3 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h | 8 ++++ .../display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 52 ++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index c17488e57eae..9c4858ca6954 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -830,7 +830,7 @@ out: return ret; } -static void abort_fused_io( +STATIC_IFN_KUNIT void abort_fused_io( struct dc_context *ctx, const struct dmub_cmd_fused_request *request ) @@ -844,6 +844,7 @@ static void abort_fused_io( io->request = *request; dm_execute_dmub_cmd(ctx, &command, DM_DMUB_WAIT_TYPE_NO_WAIT); } +EXPORT_IF_KUNIT(abort_fused_io); static bool execute_fused_io( struct amdgpu_device *dev, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h index a4a03e40ec37..ba50e1af80c1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h @@ -65,4 +65,12 @@ int dm_init_microcode(struct amdgpu_device *adev); #define FIRMWARE_RAVEN_DMCU "amdgpu/raven_dmcu.bin" #define FIRMWARE_NAVI12_DMCU "amdgpu/navi12_dmcu.bin" +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct dc_context; +struct dmub_cmd_fused_request; + +void abort_fused_io(struct dc_context *ctx, + const struct dmub_cmd_fused_request *request); +#endif + #endif /* AMDGPU_DM_AMDGPU_DM_DMUB_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c index bae34436c89e..c3bd93b15d0a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c @@ -396,6 +396,22 @@ static void dm_test_dmub_aux_fused_io_callback_max_ddc_line(struct kunit *test) KUNIT_EXPECT_EQ(test, reply_ddc_line, notify_ddc_line); } +/** + * dm_test_dmub_aux_fused_io_callback_null_args - Test the NULL-argument guard + * @test: The KUnit test context + * + * Passing a NULL device triggers the defensive guard (an ASSERT that maps to + * WARN_ON_ONCE in this build) and returns early without dereferencing the + * arguments. The call must not crash. + */ +static void dm_test_dmub_aux_fused_io_callback_null_args(struct kunit *test) +{ + struct dmub_notification notify = {}; + + /* Must not crash; guard hits ASSERT (WARN_ON_ONCE) and returns. */ + dm_dmub_aux_fused_io_callback(NULL, ¬ify); +} + /* Tests for dm_get_default_ips_mode() */ /** @@ -916,6 +932,39 @@ static void dm_test_init_microcode_unsupported_asic(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_init_microcode(adev), 0); } +/* Tests for abort_fused_io() */ + +/** + * dm_test_abort_fused_io_no_dmub_srv - Test fused IO abort is a safe no-op without DMUB service + * @test: The KUnit test context + * + * abort_fused_io() builds an abort command and submits it via + * dm_execute_dmub_cmd(); with no DC DMUB service the submission fails + * silently and the call must not crash. + */ +static void dm_test_abort_fused_io_no_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dmub_cmd_fused_request *req; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + req = kunit_kzalloc(test, sizeof(*req), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, req); + + spin_lock_init(&adev->dm.dmub_lock); + ctx->driver_context = adev; + ctx->dmub_srv = NULL; + + /* Must not crash. */ + abort_fused_io(ctx, req); +} + static struct kunit_case amdgpu_dm_dmub_tests[] = { /* dm_register_dmub_notify_callback() */ KUNIT_CASE(dm_test_register_dmub_notify_callback_null_callback), @@ -930,6 +979,7 @@ static struct kunit_case amdgpu_dm_dmub_tests[] = { /* dm_dmub_aux_fused_io_callback() */ KUNIT_CASE(dm_test_dmub_aux_fused_io_callback_copies_reply_and_completes), KUNIT_CASE(dm_test_dmub_aux_fused_io_callback_max_ddc_line), + KUNIT_CASE(dm_test_dmub_aux_fused_io_callback_null_args), /* dm_get_default_ips_mode() */ KUNIT_CASE(dm_test_get_default_ips_mode_dcn35), KUNIT_CASE(dm_test_get_default_ips_mode_dcn351), @@ -959,6 +1009,8 @@ static struct kunit_case amdgpu_dm_dmub_tests[] = { KUNIT_CASE(dm_test_dmub_sw_init_unsupported_asic), /* dm_init_microcode() */ KUNIT_CASE(dm_test_init_microcode_unsupported_asic), + /* abort_fused_io() */ + KUNIT_CASE(dm_test_abort_fused_io_no_dmub_srv), {} }; -- cgit v1.2.3 From 1d157a9aabe48eefce9c489e2623a36b199891ed Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 09:26:07 -0600 Subject: drm/amd/display: add DMUB command sync KUnit coverage [WHAT] Add KUnit coverage for the synchronous DMUB command helpers: dm_execute_dmub_cmd(), amdgpu_dm_process_dmub_aux_transfer_sync(), and amdgpu_dm_process_dmub_set_config_sync(). Cover command submission without a DC DMUB service, AUX engine-acquire failure, protocol-error propagation, the bounded reply-data copy, the zero-length reply branch, and the SET_CONFIG completed-with-unknown-error path. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 3 + .../display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 369 +++++++++++++++++++++ 2 files changed, 372 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 9c4858ca6954..7d2038100b4e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -829,6 +829,7 @@ out: mutex_unlock(&adev->dm.dpia_aux_lock); return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_process_dmub_aux_transfer_sync); STATIC_IFN_KUNIT void abort_fused_io( struct dc_context *ctx, @@ -932,6 +933,7 @@ int amdgpu_dm_process_dmub_set_config_sync( mutex_unlock(&adev->dm.dpia_aux_lock); return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_process_dmub_set_config_sync); bool dm_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type) { @@ -940,6 +942,7 @@ bool dm_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd, e guard(spinlock_irqsave)(&adev->dm.dmub_lock); return dc_dmub_srv_cmd_run(ctx->dmub_srv, cmd, wait_type); } +EXPORT_IF_KUNIT(dm_execute_dmub_cmd); bool dm_execute_dmub_cmd_list(const struct dc_context *ctx, unsigned int count, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c index c3bd93b15d0a..d75ee1c930ee 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c @@ -932,6 +932,366 @@ static void dm_test_init_microcode_unsupported_asic(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_init_microcode(adev), 0); } +/* Tests for dm_execute_dmub_cmd() */ + +/** + * dm_test_execute_dmub_cmd_null_dmub_srv - Test command execution fails without DMUB service + * @test: The KUnit test context + * + * With no DC DMUB service on the context, dc_dmub_srv_cmd_run() returns false + * and dm_execute_dmub_cmd() propagates that failure. + */ +static void dm_test_execute_dmub_cmd_null_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + union dmub_rb_cmd *cmd; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + cmd = kunit_kzalloc(test, sizeof(*cmd), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cmd); + + spin_lock_init(&adev->dm.dmub_lock); + ctx->driver_context = adev; + ctx->dmub_srv = NULL; + + KUNIT_EXPECT_FALSE(test, + dm_execute_dmub_cmd(ctx, cmd, DM_DMUB_WAIT_TYPE_NO_WAIT)); +} + +/* Tests for amdgpu_dm_process_dmub_aux_transfer_sync() */ + +/** + * dm_test_process_dmub_aux_transfer_sync_engine_acquire - Test AUX transfer engine-acquire failure + * @test: The KUnit test context + * + * With dc->link_count == 0, dc_process_dmub_aux_transfer_async() rejects the + * link index and amdgpu_dm_process_dmub_aux_transfer_sync() reports an + * engine-acquire error and returns -1. + */ +static void dm_test_process_dmub_aux_transfer_sync_engine_acquire(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc *dc; + struct aux_payload *payload; + struct dmub_notification *notify; + enum aux_return_code_type result = AUX_RET_SUCCESS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, payload); + + notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, notify); + + dc->link_count = 0; + ctx->dc = dc; + ctx->driver_context = adev; + adev->dm.dmub_notify = notify; + mutex_init(&adev->dm.dpia_aux_lock); + init_completion(&adev->dm.dmub_aux_transfer_done); + + ret = amdgpu_dm_process_dmub_aux_transfer_sync(ctx, 0, payload, &result); + + KUNIT_EXPECT_EQ(test, ret, -1); + KUNIT_EXPECT_EQ(test, result, AUX_RET_ERROR_ENGINE_ACQUIRE); +} + +/** + * dm_test_process_dmub_aux_transfer_sync_protocol_error - Test AUX protocol error result + * @test: The KUnit test context + * + * With the completion pre-signaled and a fake DC DMUB service that rejects the + * command after construction, the sync helper should propagate the notification + * result without waiting for real firmware. + */ +static void dm_test_process_dmub_aux_transfer_sync_protocol_error(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_context *dc_ctx; + struct dc *dc; + struct dc_link *link; + struct ddc_service *ddc; + struct aux_payload *payload; + struct dmub_notification *notify; + enum aux_return_code_type result = AUX_RET_SUCCESS; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ddc); + + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, payload); + + notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, notify); + + link->ddc = ddc; + dc->ctx = dc_ctx; + dc->link_count = 1; + dc->links[0] = link; + dc_ctx->dc = dc; + dc_ctx->driver_context = adev; + dc_ctx->dmub_srv = NULL; + ctx->dc = dc; + ctx->driver_context = adev; + spin_lock_init(&adev->dm.dmub_lock); + adev->dm.dmub_notify = notify; + mutex_init(&adev->dm.dpia_aux_lock); + init_completion(&adev->dm.dmub_aux_transfer_done); + complete(&adev->dm.dmub_aux_transfer_done); + notify->result = AUX_RET_ERROR_PROTOCOL_ERROR; + + ret = amdgpu_dm_process_dmub_aux_transfer_sync(ctx, 0, payload, &result); + + KUNIT_EXPECT_EQ(test, ret, -1); + KUNIT_EXPECT_EQ(test, result, AUX_RET_ERROR_PROTOCOL_ERROR); +} + +/** + * dm_test_process_dmub_aux_transfer_sync_copies_data - Test AUX reply data copy + * @test: The KUnit test context + * + * On a successful notification, the sync helper should copy the bounded reply + * data and report the high-nibble command reply when present. + */ +static void dm_test_process_dmub_aux_transfer_sync_copies_data(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_context *dc_ctx; + struct dc *dc; + struct dc_link *link; + struct ddc_service *ddc; + struct aux_payload *payload; + struct dmub_notification *notify; + enum aux_return_code_type result = AUX_RET_ERROR_UNKNOWN; + u8 data[4] = { 0 }; + u8 reply = 0; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ddc); + + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, payload); + + notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, notify); + + link->ddc = ddc; + dc->ctx = dc_ctx; + dc->link_count = 1; + dc->links[0] = link; + dc_ctx->dc = dc; + dc_ctx->driver_context = adev; + dc_ctx->dmub_srv = NULL; + ctx->dc = dc; + ctx->driver_context = adev; + spin_lock_init(&adev->dm.dmub_lock); + adev->dm.dmub_notify = notify; + mutex_init(&adev->dm.dpia_aux_lock); + init_completion(&adev->dm.dmub_aux_transfer_done); + complete(&adev->dm.dmub_aux_transfer_done); + payload->data = data; + payload->reply = &reply; + payload->length = sizeof(data); + notify->result = AUX_RET_SUCCESS; + notify->aux_reply.command = 0xA4; + notify->aux_reply.length = 3; + notify->aux_reply.data[0] = 0x11; + notify->aux_reply.data[1] = 0x22; + notify->aux_reply.data[2] = 0x33; + + ret = amdgpu_dm_process_dmub_aux_transfer_sync(ctx, 0, payload, &result); + + KUNIT_EXPECT_EQ(test, ret, 3); + KUNIT_EXPECT_EQ(test, result, AUX_RET_SUCCESS); + KUNIT_EXPECT_EQ(test, reply, 0xA); + KUNIT_EXPECT_EQ(test, data[0], 0x11); + KUNIT_EXPECT_EQ(test, data[1], 0x22); + KUNIT_EXPECT_EQ(test, data[2], 0x33); +} + +/** + * dm_test_process_dmub_aux_transfer_sync_zero_length - Test AUX reply with no data + * @test: The KUnit test context + * + * On a successful notification whose reply carries no data, the sync helper + * takes the zero-length branch and returns the reply length (0) without + * copying any payload data. + */ +static void dm_test_process_dmub_aux_transfer_sync_zero_length(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_context *dc_ctx; + struct dc *dc; + struct dc_link *link; + struct ddc_service *ddc; + struct aux_payload *payload; + struct dmub_notification *notify; + enum aux_return_code_type result = AUX_RET_ERROR_UNKNOWN; + u8 data[4] = { 0 }; + u8 reply = 0; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ddc); + + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, payload); + + notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, notify); + + link->ddc = ddc; + dc->ctx = dc_ctx; + dc->link_count = 1; + dc->links[0] = link; + dc_ctx->dc = dc; + dc_ctx->driver_context = adev; + dc_ctx->dmub_srv = NULL; + ctx->dc = dc; + ctx->driver_context = adev; + spin_lock_init(&adev->dm.dmub_lock); + adev->dm.dmub_notify = notify; + mutex_init(&adev->dm.dpia_aux_lock); + init_completion(&adev->dm.dmub_aux_transfer_done); + complete(&adev->dm.dmub_aux_transfer_done); + payload->data = data; + payload->reply = &reply; + payload->length = sizeof(data); + notify->result = AUX_RET_SUCCESS; + notify->aux_reply.command = 0x03; + notify->aux_reply.length = 0; + + ret = amdgpu_dm_process_dmub_aux_transfer_sync(ctx, 0, payload, &result); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, result, AUX_RET_SUCCESS); + KUNIT_EXPECT_EQ(test, reply, 0x3); +} + +/* Tests for amdgpu_dm_process_dmub_set_config_sync() */ + +/** + * dm_test_process_dmub_set_config_sync_unknown_error - Test SET_CONFIG completes with unknown error + * @test: The KUnit test context + * + * With no DC DMUB service, dc_process_dmub_set_config_async() cannot reach the + * firmware and reports the command as completed with SET_CONFIG_UNKNOWN_ERROR, + * so amdgpu_dm_process_dmub_set_config_sync() returns 0 with that status. + */ +static void dm_test_process_dmub_set_config_sync_unknown_error(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_context *dc_ctx; + struct dc *dc; + struct dc_link *link; + struct set_config_cmd_payload *payload; + struct dmub_notification *notify; + enum set_config_status result = SET_CONFIG_PENDING; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dc_ctx = kunit_kzalloc(test, sizeof(*dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + payload = kunit_kzalloc(test, sizeof(*payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, payload); + + notify = kunit_kzalloc(test, sizeof(*notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, notify); + + dc->ctx = dc_ctx; + dc_ctx->dmub_srv = NULL; + dc->links[0] = link; + ctx->dc = dc; + ctx->driver_context = adev; + adev->dm.dmub_notify = notify; + mutex_init(&adev->dm.dpia_aux_lock); + init_completion(&adev->dm.dmub_aux_transfer_done); + + ret = amdgpu_dm_process_dmub_set_config_sync(ctx, 0, payload, &result); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, result, SET_CONFIG_UNKNOWN_ERROR); +} + /* Tests for abort_fused_io() */ /** @@ -1009,6 +1369,15 @@ static struct kunit_case amdgpu_dm_dmub_tests[] = { KUNIT_CASE(dm_test_dmub_sw_init_unsupported_asic), /* dm_init_microcode() */ KUNIT_CASE(dm_test_init_microcode_unsupported_asic), + /* dm_execute_dmub_cmd() */ + KUNIT_CASE(dm_test_execute_dmub_cmd_null_dmub_srv), + /* amdgpu_dm_process_dmub_aux_transfer_sync() */ + KUNIT_CASE(dm_test_process_dmub_aux_transfer_sync_engine_acquire), + KUNIT_CASE(dm_test_process_dmub_aux_transfer_sync_protocol_error), + KUNIT_CASE(dm_test_process_dmub_aux_transfer_sync_copies_data), + KUNIT_CASE(dm_test_process_dmub_aux_transfer_sync_zero_length), + /* amdgpu_dm_process_dmub_set_config_sync() */ + KUNIT_CASE(dm_test_process_dmub_set_config_sync_unknown_error), /* abort_fused_io() */ KUNIT_CASE(dm_test_abort_fused_io_no_dmub_srv), {} -- cgit v1.2.3 From 24a10b9d5bf2a94629c398885ce58789e1ebea7a Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 09:27:59 -0600 Subject: drm/amd/display: add VBIOS bounding box KUnit test [WHAT] Add a KUnit test for dm_dmub_get_vbios_bounding_box() covering the default IP-version path that returns NULL without allocating GPU memory or issuing GPINT commands. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 3 ++- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 24 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 7d2038100b4e..06f6915c7ca3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -385,7 +385,7 @@ dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev, return DMUB_STATUS_TIMEOUT; } -static void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev) +STATIC_IFN_KUNIT void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev) { void *bb; long long addr; @@ -440,6 +440,7 @@ free_bb: return NULL; } +EXPORT_IF_KUNIT(dm_dmub_get_vbios_bounding_box); enum dmub_ips_disable_type dm_get_default_ips_mode( struct amdgpu_device *adev) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h index ba50e1af80c1..c53728d1c131 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h @@ -69,6 +69,7 @@ int dm_init_microcode(struct amdgpu_device *adev); struct dc_context; struct dmub_cmd_fused_request; +void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev); void abort_fused_io(struct dc_context *ctx, const struct dmub_cmd_fused_request *request); #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c index d75ee1c930ee..ead43eeb38c7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c @@ -932,6 +932,28 @@ static void dm_test_init_microcode_unsupported_asic(struct kunit *test) KUNIT_EXPECT_EQ(test, dm_init_microcode(adev), 0); } +/* Tests for dm_dmub_get_vbios_bounding_box() */ + +/** + * dm_test_dmub_get_vbios_bounding_box_default_null - Test default IP version returns NULL + * @test: The KUnit test context + * + * For an IP version without a bounding-box size mapping, the switch falls + * through to the default case and dm_dmub_get_vbios_bounding_box() returns + * NULL without allocating GPU memory or issuing GPINT commands. + */ +static void dm_test_dmub_get_vbios_bounding_box_default_null(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 0); + + KUNIT_EXPECT_NULL(test, dm_dmub_get_vbios_bounding_box(adev)); +} + /* Tests for dm_execute_dmub_cmd() */ /** @@ -1369,6 +1391,8 @@ static struct kunit_case amdgpu_dm_dmub_tests[] = { KUNIT_CASE(dm_test_dmub_sw_init_unsupported_asic), /* dm_init_microcode() */ KUNIT_CASE(dm_test_init_microcode_unsupported_asic), + /* dm_dmub_get_vbios_bounding_box() */ + KUNIT_CASE(dm_test_dmub_get_vbios_bounding_box_default_null), /* dm_execute_dmub_cmd() */ KUNIT_CASE(dm_test_execute_dmub_cmd_null_dmub_srv), /* amdgpu_dm_process_dmub_aux_transfer_sync() */ -- cgit v1.2.3 From 1eefee5466c83b61ff0889c4a11c4cc115d6d87a Mon Sep 17 00:00:00 2001 From: Tomasz Siemek Date: Tue, 7 Jul 2026 12:42:05 +0200 Subject: drm/amd/display: Drop CONFIG_DRM_AMD_DC_DCN4_2 from 3dlut code [why] CONFIG_DRM_AMD_DC_DCN4_2 were readded to DAL by mistake resulting in parts of dcn42 3dlut code not being compiled. [how] Remove readded CONFIG_DRM_AMD_DC_DCN4_2 guards. Reviewed-by: Rafal Ostrowski Signed-off-by: Tomasz Siemek Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 4 ---- drivers/gpu/drm/amd/display/dc/dc_types.h | 8 -------- drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 2 -- drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 7 +------ 4 files changed, 1 insertion(+), 20 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index e1b7d359d917..436277acd034 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1508,13 +1508,9 @@ union dc_plane_cm_flags { unsigned int blend_enable : 1; /* whether legacy (lut3d_func) or DMA is valid */ unsigned int lut3d_dma_enable : 1; -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) /* RMCM lut to be used instead of MCM */ unsigned int rmcm_enable : 1; unsigned int reserved: 27; -#else - unsigned int reserved: 28; -#endif } bits; }; diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h index 5ba7a2fffcf0..fccf9cb359f0 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h @@ -1414,28 +1414,20 @@ enum dc_cm_lut_swizzle { enum dc_cm_lut_pixel_format { CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB, -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) CM_LUT_PIXEL_FORMAT_BGRA16161616_UNORM_12MSB, -#endif CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB, -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) CM_LUT_PIXEL_FORMAT_BGRA16161616_UNORM_12LSB, -#endif CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10, -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) CM_LUT_PIXEL_FORMAT_BGRA16161616_FLOAT_FP1_5_10 -#endif }; enum dc_cm_lut_size { CM_LUT_SIZE_NONE, CM_LUT_SIZE_999, CM_LUT_SIZE_171717, -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) CM_LUT_SIZE_333333, CM_LUT_SIZE_454545, CM_LUT_SIZE_656565, -#endif }; #ifndef TRIM_CM2 diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c index d8d3208fc1af..2c71c4623d39 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c @@ -515,12 +515,10 @@ void dcn401_populate_mcm_luts(struct dc *dc, /* Select width based on the requested LUT size */ switch (cm->lut3d_dma.size) { -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) case CM_LUT_SIZE_333333: if (dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_33) width = hubp_3dlut_fl_width_33; break; -#endif // CONFIG_DRM_AMD_DC_DCN4_2 case CM_LUT_SIZE_171717: width = hubp_3dlut_fl_width_17; break; diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c index cc8e96ffe7d1..4376bf26f4ce 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c @@ -470,7 +470,6 @@ static bool dc_is_rmcm_3dlut_supported(struct hubp *hubp, struct mpc *mpc) return false; } -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) static bool is_rmcm_3dlut_fl_supported(struct dc *dc) { /* size was previously hard-coded to TRANSFORMED in local_mcm, @@ -480,7 +479,6 @@ static bool is_rmcm_3dlut_fl_supported(struct dc *dc) return false; return dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_17 != 0u; } -#endif static void dcn42_set_mcm_location_post_blend(struct dc *dc, struct pipe_ctx *pipe_ctx, bool bPostBlend) { @@ -724,9 +722,8 @@ void dcn42_populate_mcm_luts(struct dc *dc, false); //RMCM - 3dLUT+Shaper -#if defined(CONFIG_DRM_AMD_DC_DCN4_2) if (cm->flags.bits.rmcm_enable && - is_rmcm_3dlut_fl_supported(dc)) { + is_rmcm_3dlut_fl_supported(dc)) dcn42_program_rmcm_luts( hubp, pipe_ctx, @@ -734,8 +731,6 @@ void dcn42_populate_mcm_luts(struct dc *dc, mpc, lut_bank_a, mpcc_id); - } -#endif /* CONFIG_DRM_AMD_DC_DCN4_2 */ /* 1D LUT */ { -- cgit v1.2.3 From d5622bd3a7f819fc6f93415762f78023b594166a Mon Sep 17 00:00:00 2001 From: Tomasz Siemek Date: Tue, 7 Jul 2026 12:43:43 +0200 Subject: drm/amd/display: Refactor DPP_PROGRAM_GAMUT_REMAP to drop pipe_ctx param [why] Pipe_ctx shouldn't be passed as block sequence block parameter. [how] - Adjust program_gamut_remap_params struct. - Adjust program_gamut_remap interface and implementations. - Adjust program_gamut_remap callsites to match new signature. Reviewed-by: Alvin Lee Signed-off-by: Tomasz Siemek Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 10 +++++- .../gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 37 ++++++++++++++++++---- .../drm/amd/display/dc/hwss/dce110/dce110_hwseq.c | 11 ++++--- .../drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c | 20 +++++++----- .../drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h | 2 +- .../drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 2 +- .../drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 24 ++++++++------ .../drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h | 2 +- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 25 ++++++++------- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h | 2 +- drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h | 12 +++++-- 11 files changed, 99 insertions(+), 48 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 8d77158229f1..0802837d9e3f 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -831,7 +831,15 @@ bool dc_stream_set_gamut_remap(struct dc *dc, const struct dc_stream_state *stre for (i = 0; i < MAX_PIPES; i++) { if (dc->current_state->res_ctx.pipe_ctx[i].stream == stream) { pipes = &dc->current_state->res_ctx.pipe_ctx[i]; - dc->hwss.program_gamut_remap(pipes); + dc->hwss.program_gamut_remap(&(struct program_gamut_remap_params) { + .xfm = pipes->plane_res.xfm, + .dpp = pipes->plane_res.dpp, + .mpc = dc->res_pool->mpc, + .mpcc_id = pipes->plane_res.mpcc_inst, + .stream = pipes->stream, + .plane = pipes->plane_state, + .is_top_pipe = pipes->top_pipe == NULL, + }); ret = true; } } diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c index e47c8cf5d036..4f30d9ac4a0d 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c @@ -1069,7 +1069,14 @@ void hwss_build_fast_sequence(struct dc *dc, if (dc->hwss.program_gamut_remap && (current_mpc_pipe->plane_state->update_bits.gamut_remap_change || current_mpc_pipe->stream->update_flags.bits.gamut_remap)) { - block_sequence[*num_steps].params.program_gamut_remap_params.pipe_ctx = current_mpc_pipe; + struct program_gamut_remap_params *params = &block_sequence[*num_steps].params.program_gamut_remap_params; + params->dpp = current_mpc_pipe->plane_res.dpp; + params->mpc = dc->res_pool->mpc; + params->xfm = current_mpc_pipe->plane_res.xfm; + params->mpcc_id = current_mpc_pipe->plane_res.hubp->inst; + params->plane = current_mpc_pipe->plane_state; + params->stream = current_mpc_pipe->stream; + params->is_top_pipe = current_mpc_pipe->top_pipe == NULL; block_sequence[*num_steps].func = DPP_PROGRAM_GAMUT_REMAP; (*num_steps)++; } @@ -1236,7 +1243,8 @@ void hwss_execute_sequence(struct dc *dc, params->set_input_transfer_func_params.plane_state); break; case DPP_PROGRAM_GAMUT_REMAP: - hwss_program_gamut_remap(params); + if (dc->hwss.program_gamut_remap) + dc->hwss.program_gamut_remap(¶ms->program_gamut_remap_params); break; case HUBP_ENABLE_3DLUT_FL: hwss_hubp_enable_3dlut_fl(params); @@ -1779,7 +1787,14 @@ void hwss_add_dpp_program_gamut_remap(struct block_sequence_state *seq_state, struct pipe_ctx *pipe_ctx) { if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { - seq_state->steps[*seq_state->num_steps].params.program_gamut_remap_params.pipe_ctx = pipe_ctx; + struct program_gamut_remap_params *params = &seq_state->steps[*seq_state->num_steps].params.program_gamut_remap_params; + params->xfm = pipe_ctx->plane_res.xfm; + params->dpp = pipe_ctx->plane_res.dpp; + params->mpc = pipe_ctx->stream->ctx->dc->res_pool->mpc; + params->mpcc_id = pipe_ctx->plane_res.hubp->inst; + params->plane = pipe_ctx->plane_state; + params->stream = pipe_ctx->stream; + params->is_top_pipe = pipe_ctx->top_pipe == NULL; seq_state->steps[*seq_state->num_steps].func = DPP_PROGRAM_GAMUT_REMAP; (*seq_state->num_steps)++; } @@ -3617,12 +3632,20 @@ void hwss_set_cursor_sdr_white_level(union block_sequence_params *params) dc->hwss.set_cursor_sdr_white_level(pipe_ctx); } -void hwss_program_gamut_remap(union block_sequence_params *params) +void hwss_program_gamut_remap(struct pipe_ctx *pipe_ctx) { - struct dc *dc = params->program_gamut_remap_params.pipe_ctx->stream->ctx->dc; + struct dc *dc = pipe_ctx->stream->ctx->dc; - if (dc && dc->hwss.program_gamut_remap) - dc->hwss.program_gamut_remap(params->program_gamut_remap_params.pipe_ctx); + if (dc->hwss.program_gamut_remap) + dc->hwss.program_gamut_remap(&(struct program_gamut_remap_params) { + .xfm = pipe_ctx->plane_res.xfm, + .dpp = pipe_ctx->plane_res.dpp, + .mpc = dc->res_pool->mpc, + .mpcc_id = pipe_ctx->plane_res.hubp->inst, + .stream = pipe_ctx->stream, + .plane = pipe_ctx->plane_state, + .is_top_pipe = pipe_ctx->top_pipe == NULL, + }); } void hwss_program_output_csc(union block_sequence_params *params) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index cce4f3065575..74b046ab3bc3 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -2810,23 +2810,26 @@ static void program_surface_visibility(const struct dc *dc, } -static void program_gamut_remap(struct pipe_ctx *pipe_ctx) +static void program_gamut_remap(struct program_gamut_remap_params *params) { + struct transform *xfm = params->xfm; + const struct dc_stream_state *stream = params->stream; int i = 0; struct xfm_grph_csc_adjustment adjust; + memset(&adjust, 0, sizeof(adjust)); adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; - if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) { + if (stream->gamut_remap_matrix.enable_remap == true) { adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) adjust.temperature_matrix[i] = - pipe_ctx->stream->gamut_remap_matrix.matrix[i]; + stream->gamut_remap_matrix.matrix[i]; } - pipe_ctx->plane_res.xfm->funcs->transform_set_gamut_remap(pipe_ctx->plane_res.xfm, &adjust); + xfm->funcs->transform_set_gamut_remap(xfm, &adjust); } static void update_plane_addr(const struct dc *dc, struct pipe_ctx *pipe_ctx) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c index 541cd908b341..5b8b14f43a0c 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c @@ -2814,28 +2814,32 @@ static void dcn10_enable_plane( } -void dcn10_program_gamut_remap(struct pipe_ctx *pipe_ctx) +void dcn10_program_gamut_remap(struct program_gamut_remap_params *params) { + struct dpp *dpp = params->dpp; + const struct dc_stream_state *stream = params->stream; + const struct dc_plane_state *plane = params->plane; int i = 0; struct dpp_grph_csc_adjustment adjust; + memset(&adjust, 0, sizeof(adjust)); adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; - if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) { + if (stream->gamut_remap_matrix.enable_remap == true) { adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) adjust.temperature_matrix[i] = - pipe_ctx->stream->gamut_remap_matrix.matrix[i]; - } else if (pipe_ctx->plane_state && - pipe_ctx->plane_state->gamut_remap_matrix.enable_remap == true) { + stream->gamut_remap_matrix.matrix[i]; + } else if (plane && + plane->gamut_remap_matrix.enable_remap == true) { adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) adjust.temperature_matrix[i] = - pipe_ctx->plane_state->gamut_remap_matrix.matrix[i]; + plane->gamut_remap_matrix.matrix[i]; } - pipe_ctx->plane_res.dpp->funcs->dpp_set_gamut_remap(pipe_ctx->plane_res.dpp, &adjust); + dpp->funcs->dpp_set_gamut_remap(dpp, &adjust); } @@ -3152,7 +3156,7 @@ static void dcn10_update_dchubp_dpp( if (plane_state->update_bits.full_update) { /*gamut remap*/ - dc->hwss.program_gamut_remap(pipe_ctx); + hwss_program_gamut_remap(pipe_ctx); dc->hwss.program_output_csc(dc, pipe_ctx, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h index 476095c5dd0c..162972dfdbe8 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h @@ -108,7 +108,7 @@ void dcn10_program_pipe( struct dc *dc, struct pipe_ctx *pipe_ctx, struct dc_state *context); -void dcn10_program_gamut_remap(struct pipe_ctx *pipe_ctx); +void dcn10_program_gamut_remap(struct program_gamut_remap_params *params); void dcn10_init_hw(struct dc *dc); void dcn10_init_pipes(struct dc *dc, struct dc_state *context); void dcn10_power_down_on_boot(struct dc *dc); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c index 95e5b6a6ba0f..83794d5b838c 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c @@ -1815,7 +1815,7 @@ void dcn20_update_dchubp_dpp( || plane_state->update_bits.gamut_remap_change || pipe_ctx->stream->update_flags.bits.out_csc) { /* dpp/cm gamut remap*/ - dc->hwss.program_gamut_remap(pipe_ctx); + hwss_program_gamut_remap(pipe_ctx); /*call the dcn2 method which uses mpc csc*/ dc->hwss.program_output_csc(dc, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c index aa7707b2b25b..82a662efa49f 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c @@ -357,37 +357,41 @@ bool dcn30_set_input_transfer_func(struct dc *dc, return result; } -void dcn30_program_gamut_remap(struct pipe_ctx *pipe_ctx) +void dcn30_program_gamut_remap(struct program_gamut_remap_params *params) { + struct dpp *dpp = params->dpp; + struct mpc *mpc = params->mpc; + int mpcc_id = params->mpcc_id; + const struct dc_stream_state *stream = params->stream; + const struct dc_plane_state *plane = params->plane; + bool is_top_pipe = params->is_top_pipe; int i = 0; struct dpp_grph_csc_adjustment dpp_adjust; struct mpc_grph_gamut_adjustment mpc_adjust; - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; memset(&dpp_adjust, 0, sizeof(dpp_adjust)); dpp_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; - if (pipe_ctx->plane_state && - pipe_ctx->plane_state->gamut_remap_matrix.enable_remap == true) { + if (plane && + plane->gamut_remap_matrix.enable_remap == true) { dpp_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) dpp_adjust.temperature_matrix[i] = - pipe_ctx->plane_state->gamut_remap_matrix.matrix[i]; + plane->gamut_remap_matrix.matrix[i]; } - pipe_ctx->plane_res.dpp->funcs->dpp_set_gamut_remap(pipe_ctx->plane_res.dpp, + dpp->funcs->dpp_set_gamut_remap(dpp, &dpp_adjust); memset(&mpc_adjust, 0, sizeof(mpc_adjust)); mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; - if (pipe_ctx->top_pipe == NULL) { - if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) { + if (is_top_pipe) { + if (stream->gamut_remap_matrix.enable_remap == true) { mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) mpc_adjust.temperature_matrix[i] = - pipe_ctx->stream->gamut_remap_matrix.matrix[i]; + stream->gamut_remap_matrix.matrix[i]; } } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h index a963d360a368..4182cf399424 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h @@ -63,7 +63,7 @@ bool dcn30_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); -void dcn30_program_gamut_remap(struct pipe_ctx *pipe_ctx); +void dcn30_program_gamut_remap(struct program_gamut_remap_params *params); bool dcn30_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c index 2c71c4623d39..5f8bc774acbe 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c @@ -89,28 +89,31 @@ void dcn401_initialize_min_clocks(struct dc *dc) true); } -void dcn401_program_gamut_remap(struct pipe_ctx *pipe_ctx) +void dcn401_program_gamut_remap(struct program_gamut_remap_params *params) { + struct mpc *mpc = params->mpc; + int mpcc_id = params->mpcc_id; + const struct dc_stream_state *stream = params->stream; + const struct dc_plane_state *plane = params->plane; + bool is_top_pipe = params->is_top_pipe; unsigned int i = 0; struct mpc_grph_gamut_adjustment mpc_adjust; - unsigned int mpcc_id = pipe_ctx->plane_res.mpcc_inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; //For now assert if location is not pre-blend - if (pipe_ctx->plane_state) - ASSERT(pipe_ctx->plane_state->mcm_location == MPCC_MOVABLE_CM_LOCATION_BEFORE); + if (plane) + ASSERT(plane->mcm_location == MPCC_MOVABLE_CM_LOCATION_BEFORE); // program MPCC_MCM_FIRST_GAMUT_REMAP memset(&mpc_adjust, 0, sizeof(mpc_adjust)); mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; mpc_adjust.mpcc_gamut_remap_block_id = MPCC_MCM_FIRST_GAMUT_REMAP; - if (pipe_ctx->plane_state && - pipe_ctx->plane_state->gamut_remap_matrix.enable_remap == true) { + if (plane && + plane->gamut_remap_matrix.enable_remap == true) { mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) mpc_adjust.temperature_matrix[i] = - pipe_ctx->plane_state->gamut_remap_matrix.matrix[i]; + plane->gamut_remap_matrix.matrix[i]; } mpc->funcs->set_gamut_remap(mpc, mpcc_id, &mpc_adjust); @@ -126,12 +129,12 @@ void dcn401_program_gamut_remap(struct pipe_ctx *pipe_ctx) mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; mpc_adjust.mpcc_gamut_remap_block_id = MPCC_OGAM_GAMUT_REMAP; - if (pipe_ctx->top_pipe == NULL) { - if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) { + if (is_top_pipe) { + if (stream->gamut_remap_matrix.enable_remap == true) { mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) mpc_adjust.temperature_matrix[i] = - pipe_ctx->stream->gamut_remap_matrix.matrix[i]; + stream->gamut_remap_matrix.matrix[i]; } } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h index a760050eea8c..6d2e93149811 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h @@ -32,7 +32,7 @@ struct ips_ono_region_state { uint32_t current_pwr_state; }; -void dcn401_program_gamut_remap(struct pipe_ctx *pipe_ctx); +void dcn401_program_gamut_remap(struct program_gamut_remap_params *params); void dcn401_init_hw(struct dc *dc); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h index 65df8002d3d7..d8398b39a119 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h @@ -95,7 +95,13 @@ struct set_input_transfer_func_params { }; struct program_gamut_remap_params { - struct pipe_ctx *pipe_ctx; + struct transform *xfm; + struct dpp *dpp; + struct mpc *mpc; + int mpcc_id; + const struct dc_stream_state *stream; + const struct dc_plane_state *plane; + bool is_top_pipe; }; struct hubp_enable_3dlut_fl_params { @@ -1389,7 +1395,7 @@ struct hw_sequencer_funcs { void (*program_cursor_offload_now)(struct dc *dc, const struct pipe_ctx *pipe); /* Colour Related */ - void (*program_gamut_remap)(struct pipe_ctx *pipe_ctx); + void (*program_gamut_remap)(struct program_gamut_remap_params *params); void (*program_output_csc)(struct dc *dc, struct pipe_ctx *pipe_ctx, enum dc_color_space colorspace, uint16_t *matrix, int opp_id); @@ -1940,7 +1946,7 @@ void hwss_set_cursor_position(union block_sequence_params *params); void hwss_set_cursor_sdr_white_level(union block_sequence_params *params); -void hwss_program_gamut_remap(union block_sequence_params *params); +void hwss_program_gamut_remap(struct pipe_ctx *pipe_ctx); void hwss_program_output_csc(union block_sequence_params *params); -- cgit v1.2.3 From 6bfca938471b1804c4a63774ca2fd30cb3596215 Mon Sep 17 00:00:00 2001 From: Tomasz Siemek Date: Tue, 7 Jul 2026 17:39:04 +0200 Subject: drm/amd/display: Refactor DPP_SET_OUTPUT_TRANSFER_FUNC to drop pipe_ctx [why] Pipe_ctx shouldn't be passed as block sequence block parameter. [how] Adjust arguments for set_output_transfer_func and implementations. Reviewed-by: Alvin Lee Signed-off-by: Tomasz Siemek Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 50 ++++++++++++++++------ .../drm/amd/display/dc/hwss/dce110/dce110_hwseq.c | 9 ++-- .../drm/amd/display/dc/hwss/dce60/dce60_hwseq.c | 2 +- .../drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c | 11 +++-- .../drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h | 3 +- .../drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 20 +++++---- .../drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h | 3 +- .../drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 31 +++++++------- .../drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h | 4 +- .../drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 31 ++++++-------- .../drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h | 8 ++-- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 22 +++++----- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h | 4 +- drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h | 11 +++-- .../drm/amd/display/dc/hwss/hw_sequencer_private.h | 6 +-- 15 files changed, 119 insertions(+), 96 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c index 4f30d9ac4a0d..6002175420a0 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c @@ -1111,10 +1111,16 @@ void hwss_build_fast_sequence(struct dc *dc, (*num_steps)++; } } - if (hws->funcs.set_output_transfer_func && current_mpc_pipe->stream->update_flags.bits.out_tf) { - block_sequence[*num_steps].params.set_output_transfer_func_params.dc = dc; - block_sequence[*num_steps].params.set_output_transfer_func_params.pipe_ctx = current_mpc_pipe; - block_sequence[*num_steps].params.set_output_transfer_func_params.stream = current_mpc_pipe->stream; + if (current_mpc_pipe->stream->update_flags.bits.out_tf) { + struct set_output_transfer_func_params *otf_params = + &block_sequence[*num_steps].params.set_output_transfer_func_params; + + otf_params->dpp = current_mpc_pipe->plane_res.dpp; + otf_params->xfm = current_mpc_pipe->plane_res.xfm; + otf_params->mpc = dc->res_pool->mpc; + otf_params->mpcc_id = current_mpc_pipe->plane_res.hubp->inst; + otf_params->is_top_pipe = resource_is_pipe_type(pipe_ctx, OPP_HEAD); + otf_params->stream = current_mpc_pipe->stream; block_sequence[*num_steps].func = DPP_SET_OUTPUT_TRANSFER_FUNC; (*num_steps)++; } @@ -1271,9 +1277,7 @@ void hwss_execute_sequence(struct dc *dc, hwss_program_manual_trigger(params); break; case DPP_SET_OUTPUT_TRANSFER_FUNC: - hws->funcs.set_output_transfer_func(params->set_output_transfer_func_params.dc, - params->set_output_transfer_func_params.pipe_ctx, - params->set_output_transfer_func_params.stream); + hws->funcs.set_output_transfer_func(¶ms->set_output_transfer_func_params); break; case MPC_UPDATE_VISUAL_CONFIRM: dc->hwss.update_visual_confirm_color(params->update_visual_confirm_params.dc, @@ -1829,19 +1833,39 @@ void hwss_add_optc_program_manual_trigger(struct block_sequence_state *seq_state * Helper function to add DPP set output transfer function to block sequence */ void hwss_add_dpp_set_output_transfer_func(struct block_sequence_state *seq_state, - struct dc *dc, - struct pipe_ctx *pipe_ctx, - struct dc_stream_state *stream) + struct dc *dc, struct pipe_ctx *pipe_ctx) { if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { - seq_state->steps[*seq_state->num_steps].params.set_output_transfer_func_params.dc = dc; - seq_state->steps[*seq_state->num_steps].params.set_output_transfer_func_params.pipe_ctx = pipe_ctx; - seq_state->steps[*seq_state->num_steps].params.set_output_transfer_func_params.stream = stream; + seq_state->steps[*seq_state->num_steps].params.set_output_transfer_func_params = + (struct set_output_transfer_func_params) { + .xfm = pipe_ctx->plane_res.xfm, + .dpp = pipe_ctx->plane_res.dpp, + .mpc = dc->res_pool->mpc, + .mpcc_id = pipe_ctx->plane_res.hubp->inst, + .is_top_pipe = resource_is_pipe_type(pipe_ctx, OPP_HEAD), + .stream = pipe_ctx->stream, + }; seq_state->steps[*seq_state->num_steps].func = DPP_SET_OUTPUT_TRANSFER_FUNC; (*seq_state->num_steps)++; } } +void hwss_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx) +{ + if (dc->hwseq->funcs.set_output_transfer_func) { + dc->hwseq->funcs.set_output_transfer_func( + &(struct set_output_transfer_func_params) { + .xfm = pipe_ctx->plane_res.xfm, + .dpp = pipe_ctx->plane_res.dpp, + .mpc = dc->res_pool->mpc, + .mpcc_id = pipe_ctx->plane_res.hubp->inst, + .is_top_pipe = resource_is_pipe_type(pipe_ctx, OPP_HEAD), + .stream = pipe_ctx->stream, + } + ); + } +} + /* * Helper function to add MPC update visual confirm to block sequence */ diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index 74b046ab3bc3..4830a0d94177 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -607,11 +607,10 @@ dce110_translate_regamma_to_hw_format(const struct dc_transfer_func *output_tf, } static bool -dce110_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +dce110_set_output_transfer_func(struct set_output_transfer_func_params *params) { - (void)dc; - struct transform *xfm = pipe_ctx->plane_res.xfm; + struct transform *xfm = params->xfm; + const struct dc_stream_state *stream = params->stream; xfm->funcs->opp_power_on_regamma_lut(xfm, true); xfm->regamma_params.hw_points_num = GAMMA_HW_POINTS_NUM; @@ -3181,7 +3180,7 @@ static void dce110_program_front_end_for_pipe( hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); if (pipe_ctx->plane_state->update_bits.full_update) - hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); + hwss_set_output_transfer_func(dc, pipe_ctx); DC_LOG_SURFACE( "Pipe:%d %p: addr hi:0x%x, " diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c index 26aa303b8237..221996b348ab 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c @@ -338,7 +338,7 @@ dce60_program_front_end_for_pipe( hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); if (pipe_ctx->plane_state->update_bits.full_update) - hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); + hwss_set_output_transfer_func(dc, pipe_ctx); DC_LOG_SURFACE( "Pipe:%d %p: addr hi:0x%x, " diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c index 5b8b14f43a0c..22c7535fd9c4 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c @@ -2154,10 +2154,11 @@ static void log_tf(struct dc_context *ctx, } } -bool dcn10_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +bool dcn10_set_output_transfer_func(struct set_output_transfer_func_params *params) { - struct dpp *dpp = pipe_ctx->plane_res.dpp; + struct dpp *dpp = params->dpp; + const struct dc_stream_state *stream = params->stream; + struct dc *dc; if (!stream) return false; @@ -2165,6 +2166,8 @@ bool dcn10_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, if (dpp == NULL) return false; + dc = dpp->ctx->dc; + dpp->regamma_params.hw_points_num = GAMMA_HW_POINTS_NUM; if (stream->out_transfer_func.type == TF_TYPE_PREDEFINED && @@ -3301,7 +3304,7 @@ void dcn10_program_pipe( * doing heavy calculation and programming */ if (pipe_ctx->plane_state->update_bits.full_update) - hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); + hwss_set_output_transfer_func(dc, pipe_ctx); } void dcn10_wait_for_pending_cleared(struct dc *dc, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h index 162972dfdbe8..2cb674ba54e1 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.h @@ -73,8 +73,7 @@ void dcn10_program_output_csc(struct dc *dc, enum dc_color_space colorspace, uint16_t *matrix, int opp_id); -bool dcn10_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); +bool dcn10_set_output_transfer_func(struct set_output_transfer_func_params *params); bool dcn10_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); void dcn10_update_plane_addr(const struct dc *dc, struct pipe_ctx *pipe_ctx); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c index 83794d5b838c..98778d5e114e 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c @@ -1019,26 +1019,30 @@ void dcn20_program_output_csc(struct dc *dc, } } -bool dcn20_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +bool dcn20_set_output_transfer_func(struct set_output_transfer_func_params *otf_params) { - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dpp *dpp = otf_params->dpp; + struct mpc *mpc = otf_params->mpc; + int mpcc_id = otf_params->mpcc_id; + bool is_top_pipe = otf_params->is_top_pipe; + const struct dc_stream_state *stream = otf_params->stream; + struct dc *dc = dpp->ctx->dc; const struct pwl_params *params = NULL; + /* * program OGAM only for the top pipe * if there is a pipe split then fix diagnostic is required: * how to pass OGAM parameter for stream. * if programming for all pipes is required then remove condition - * pipe_ctx->top_pipe == NULL ,but then fix the diagnostic. + * is_top_pipe ,but then fix the diagnostic. */ if (mpc->funcs->power_on_mpc_mem_pwr) mpc->funcs->power_on_mpc_mem_pwr(mpc, mpcc_id, true); - if (pipe_ctx->top_pipe == NULL + if (is_top_pipe && mpc->funcs->set_output_gamma) { if (stream->out_transfer_func.type == TF_TYPE_HWPWL) params = &stream->out_transfer_func.pwl; - else if (pipe_ctx->stream->out_transfer_func.type == + else if (stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && cm_helper_translate_curve_to_hw_format(dc->ctx, &stream->out_transfer_func, @@ -1991,7 +1995,7 @@ static void dcn20_program_pipe( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->stream->update_flags.bits.out_tf) - hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); + hwss_set_output_transfer_func(dc, pipe_ctx); /* If the pipe has been enabled or has a different opp, we * should reprogram the fmt. This deals with cases where diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h index 9d1ad3b29ca5..749348d3c793 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.h @@ -44,8 +44,7 @@ void dcn20_update_plane_addr(const struct dc *dc, struct pipe_ctx *pipe_ctx); void dcn20_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx); bool dcn20_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); -bool dcn20_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); +bool dcn20_set_output_transfer_func(struct set_output_transfer_func_params *params); void dcn20_program_output_csc(struct dc *dc, struct pipe_ctx *pipe_ctx, enum dc_color_space colorspace, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c index 82a662efa49f..59184e146fc9 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c @@ -256,13 +256,10 @@ bool dcn30_set_blend_lut( return result; } -static bool dcn30_set_mpc_shaper_3dlut(struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +static bool dcn30_set_mpc_shaper_3dlut(struct dpp *dpp, struct mpc *mpc, + int mpcc_id, const struct dc_stream_state *stream) { - struct dpp *dpp_base = pipe_ctx->plane_res.dpp; - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct dc *dc = pipe_ctx->stream->ctx->dc; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dc *dc = dpp->ctx->dc; bool result = false; uint32_t acquired_rmu = 0; int mpcc_id_projected = 0; @@ -274,8 +271,8 @@ static bool dcn30_set_mpc_shaper_3dlut(struct pipe_ctx *pipe_ctx, shaper_lut = &stream->func_shaper->pwl; } else if (stream->func_shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { cm_helper_translate_curve_to_hw_format(stream->ctx, stream->func_shaper, - &dpp_base->shaper_params, true); - shaper_lut = &dpp_base->shaper_params; + &dpp->shaper_params, true); + shaper_lut = &dpp->shaper_params; } } @@ -398,23 +395,25 @@ void dcn30_program_gamut_remap(struct program_gamut_remap_params *params) mpc->funcs->set_gamut_remap(mpc, mpcc_id, &mpc_adjust); } -bool dcn30_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +bool dcn30_set_output_transfer_func(struct set_output_transfer_func_params *otf_params) { - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dpp *dpp = otf_params->dpp; + struct mpc *mpc = otf_params->mpc; + int mpcc_id = otf_params->mpcc_id; + bool is_top_pipe = otf_params->is_top_pipe; + const struct dc_stream_state *stream = otf_params->stream; + struct dc *dc = dpp->ctx->dc; const struct pwl_params *params = NULL; bool ret = false; /* program OGAM or 3DLUT only for the top pipe*/ - if (pipe_ctx->top_pipe == NULL) { + if (is_top_pipe) { /*program rmu shaper and 3dlut in MPC*/ - ret = dcn30_set_mpc_shaper_3dlut(pipe_ctx, stream); + ret = dcn30_set_mpc_shaper_3dlut(dpp, mpc, mpcc_id, stream); if (ret == false && mpc->funcs->set_output_gamma) { if (stream->out_transfer_func.type == TF_TYPE_HWPWL) params = &stream->out_transfer_func.pwl; - else if (pipe_ctx->stream->out_transfer_func.type == + else if (stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && cm3_helper_translate_curve_to_hw_format(stream->ctx, &stream->out_transfer_func, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h index 4182cf399424..0399642076eb 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.h @@ -65,9 +65,7 @@ bool dcn30_set_input_transfer_func(struct dc *dc, void dcn30_program_gamut_remap(struct program_gamut_remap_params *params); -bool dcn30_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); +bool dcn30_set_output_transfer_func(struct set_output_transfer_func_params *params); void dcn30_set_avmute(struct pipe_ctx *pipe_ctx, bool enable); void dcn30_update_info_frame(struct pipe_ctx *pipe_ctx); void dcn30_program_dmdata_engine(struct pipe_ctx *pipe_ctx); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c index c2ea25927765..c5f6eb482f07 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c @@ -443,13 +443,10 @@ void dcn32_subvp_pipe_control_lock_fast(union block_sequence_params *params) } } -bool dcn32_set_mpc_shaper_3dlut( - struct pipe_ctx *pipe_ctx, const struct dc_stream_state *stream) +bool dcn32_set_mpc_shaper_3dlut(struct dpp *dpp, struct mpc *mpc, + int mpcc_id, const struct dc_stream_state *stream) { - struct dpp *dpp_base = pipe_ctx->plane_res.dpp; - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct dc *dc = pipe_ctx->stream->ctx->dc; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dc *dc = dpp->ctx->dc; bool result = false; const struct pwl_params *shaper_lut = NULL; @@ -460,8 +457,8 @@ bool dcn32_set_mpc_shaper_3dlut( else if (stream->func_shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { cm_helper_translate_curve_to_hw_format(stream->ctx, stream->func_shaper, - &dpp_base->shaper_params, true); - shaper_lut = &dpp_base->shaper_params; + &dpp->shaper_params, true); + shaper_lut = &dpp->shaper_params; } } @@ -566,24 +563,24 @@ bool dcn32_set_input_transfer_func(struct dc *dc, return result; } -bool dcn32_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +bool dcn32_set_output_transfer_func(struct set_output_transfer_func_params *otf_params) { - (void)dc; - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dpp *dpp = otf_params->dpp; + struct mpc *mpc = otf_params->mpc; + int mpcc_id = otf_params->mpcc_id; + bool is_top_pipe = otf_params->is_top_pipe; + const struct dc_stream_state *stream = otf_params->stream; const struct pwl_params *params = NULL; bool ret = false; /* program OGAM or 3DLUT only for the top pipe*/ - if (resource_is_pipe_type(pipe_ctx, OPP_HEAD)) { + if (is_top_pipe) { /*program shaper and 3dlut in MPC*/ - ret = dcn32_set_mpc_shaper_3dlut(pipe_ctx, stream); + ret = dcn32_set_mpc_shaper_3dlut(dpp, mpc, mpcc_id, stream); if (ret == false && mpc->funcs->set_output_gamma) { if (stream->out_transfer_func.type == TF_TYPE_HWPWL) params = &stream->out_transfer_func.pwl; - else if (pipe_ctx->stream->out_transfer_func.type == + else if (stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && cm3_helper_translate_curve_to_hw_format(stream->ctx, &stream->out_transfer_func, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h index 0303a5953673..090d94d38343 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.h @@ -54,12 +54,10 @@ bool dcn32_set_input_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); -bool dcn32_set_mpc_shaper_3dlut( - struct pipe_ctx *pipe_ctx, const struct dc_stream_state *stream); +bool dcn32_set_mpc_shaper_3dlut(struct dpp *dpp_base, struct mpc *mpc, + int mpcc_id, const struct dc_stream_state *stream); -bool dcn32_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); +bool dcn32_set_output_transfer_func(struct set_output_transfer_func_params *params); void dcn32_init_hw(struct dc *dc); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c index 5f8bc774acbe..797d6fb9328d 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c @@ -696,24 +696,24 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, return result; } -bool dcn401_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) +bool dcn401_set_output_transfer_func(struct set_output_transfer_func_params *otf_params) { - (void)dc; - int mpcc_id = pipe_ctx->plane_res.hubp->inst; - struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; + struct dpp *dpp = otf_params->dpp; + struct mpc *mpc = otf_params->mpc; + int mpcc_id = otf_params->mpcc_id; + bool is_top_pipe = otf_params->is_top_pipe; + const struct dc_stream_state *stream = otf_params->stream; const struct pwl_params *params = NULL; bool ret = false; /* program OGAM or 3DLUT only for the top pipe*/ - if (resource_is_pipe_type(pipe_ctx, OPP_HEAD)) { + if (is_top_pipe) { /*program shaper and 3dlut in MPC*/ - ret = dcn32_set_mpc_shaper_3dlut(pipe_ctx, stream); + ret = dcn32_set_mpc_shaper_3dlut(dpp, mpc, mpcc_id, stream); if (ret == false && mpc->funcs->set_output_gamma) { if (stream->out_transfer_func.type == TF_TYPE_HWPWL) params = &stream->out_transfer_func.pwl; - else if (pipe_ctx->stream->out_transfer_func.type == + else if (stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && cm3_helper_translate_curve_to_hw_format(stream->ctx, &stream->out_transfer_func, @@ -2397,7 +2397,7 @@ void dcn401_program_pipe( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->stream->update_flags.bits.out_tf) - hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); + hwss_set_output_transfer_func(dc, pipe_ctx); /* If the pipe has been enabled or has a different opp, we * should reprogram the fmt. This deals with cases where @@ -2555,7 +2555,7 @@ void dcn401_program_pipe_sequence( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->stream->update_flags.bits.out_tf) { - hwss_add_dpp_set_output_transfer_func(seq_state, dc, pipe_ctx, pipe_ctx->stream); + hwss_add_dpp_set_output_transfer_func(seq_state, dc, pipe_ctx); } /* If the pipe has been enabled or has a different opp, we diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h index 6d2e93149811..f90e25243ead 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h @@ -38,9 +38,7 @@ void dcn401_init_hw(struct dc *dc); bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); -bool dcn401_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); +bool dcn401_set_output_transfer_func(struct set_output_transfer_func_params *params); void dcn401_trigger_3dlut_dma_load(struct dc *dc, struct pipe_ctx *pipe_ctx); void dcn401_calculate_dccg_tmds_div_value(struct pipe_ctx *pipe_ctx, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h index d8398b39a119..e8bf96a7d63a 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h @@ -138,8 +138,11 @@ struct program_bias_and_scale_params { }; struct set_output_transfer_func_params { - struct dc *dc; - struct pipe_ctx *pipe_ctx; + struct transform *xfm; + struct dpp *dpp; + struct mpc *mpc; + int mpcc_id; + bool is_top_pipe; const struct dc_stream_state *stream; }; @@ -1993,7 +1996,9 @@ void hwss_add_optc_program_manual_trigger(struct block_sequence_state *seq_state struct pipe_ctx *pipe_ctx); void hwss_add_dpp_set_output_transfer_func(struct block_sequence_state *seq_state, - struct dc *dc, struct pipe_ctx *pipe_ctx, struct dc_stream_state *stream); + struct dc *dc, struct pipe_ctx *pipe_ctx); + +void hwss_set_output_transfer_func(struct dc *dc, struct pipe_ctx *pipe_ctx); void hwss_add_mpc_update_visual_confirm(struct block_sequence_state *seq_state, struct dc *dc, struct pipe_ctx *pipe_ctx, int mpcc_id); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h index b4956893ae9a..31ace62a37d9 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h @@ -67,6 +67,8 @@ struct dc_phy_addr_space_config; struct dc_virtual_addr_space_config; struct hubp; struct dpp; +struct transform; +struct mpc; struct dce_hwseq; struct timing_generator; struct tg_color; @@ -92,9 +94,7 @@ struct hwseq_private_funcs { bool (*set_input_transfer_func)(struct dc *dc, struct pipe_ctx *pipe_ctx, const struct dc_plane_state *plane_state); - bool (*set_output_transfer_func)(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); + bool (*set_output_transfer_func)(struct set_output_transfer_func_params *params); void (*power_down)(struct dc *dc); void (*enable_display_pipe_clock_gating)(struct dc_context *ctx, bool clock_gating); -- cgit v1.2.3 From b1cb2fc140024e3d3caf5f348495990945c7ac85 Mon Sep 17 00:00:00 2001 From: Ilya Bakoulin Date: Tue, 30 Jun 2026 17:57:18 -0400 Subject: drm/amd/display: Fix DP LT failure logging [Why/How] The final DP LT failure meant to be logged as DC warning is skipped due to a break statement above. Move logging up to make sure we don't miss LT fail events. Reviewed-by: George Shen Signed-off-by: Ilya Bakoulin Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/dc/link/protocols/link_dp_training.c | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c index 605bf19dc4f2..04eedec8a230 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c @@ -1721,6 +1721,20 @@ bool perform_link_training_with_retries( } } + if (j == (attempts - 1)) { + DC_LOG_WARNING( + "%s: Link(%d) training attempt %u of %d failed @ rate(%d) x lane(%d) @ spread = %x : fail reason:(%d)\n", + __func__, link->link_index, (unsigned int)j + 1, attempts, + cur_link_settings.link_rate, cur_link_settings.lane_count, + cur_link_settings.link_spread, status); + } else { + DC_LOG_HW_LINK_TRAINING( + "%s: Link(%d) training attempt %u of %d failed @ rate(%d) x lane(%d) @ spread = %x : fail reason:(%d)\n", + __func__, link->link_index, (unsigned int)j + 1, attempts, + cur_link_settings.link_rate, cur_link_settings.lane_count, + cur_link_settings.link_spread, status); + } + fail_count++; dp_trace_lt_fail_count_update(link, fail_count, false); if (link->ep_type == DISPLAY_ENDPOINT_PHY) { @@ -1740,20 +1754,6 @@ bool perform_link_training_with_retries( do_fallback = false; } - if (j == (attempts - 1)) { - DC_LOG_WARNING( - "%s: Link(%d) training attempt %u of %d failed @ rate(%d) x lane(%d) @ spread = %x : fail reason:(%d)\n", - __func__, link->link_index, (unsigned int)j + 1, attempts, - cur_link_settings.link_rate, cur_link_settings.lane_count, - cur_link_settings.link_spread, status); - } else { - DC_LOG_HW_LINK_TRAINING( - "%s: Link(%d) training attempt %u of %d failed @ rate(%d) x lane(%d) @ spread = %x : fail reason:(%d)\n", - __func__, link->link_index, (unsigned int)j + 1, attempts, - cur_link_settings.link_rate, cur_link_settings.lane_count, - cur_link_settings.link_spread, status); - } - dp_disable_link_phy(link, &pipe_ctx->link_res, signal); /* Abort link training if failure due to sink being unplugged. */ -- cgit v1.2.3 From 889afb51af905e32e60827487a57bd380d9fa52c Mon Sep 17 00:00:00 2001 From: Dillon Varone Date: Mon, 22 Jun 2026 11:42:39 -0400 Subject: drm/amd/display: Add updated MCIF ARB register definitions [WHY&HOW] DCN4+ use a new structure for MCIF arbiter registers. Reviewed-by: Austin Zheng Signed-off-by: Dillon Varone Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/inc/hw/mcif_wb.h | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/mcif_wb.h b/drivers/gpu/drm/amd/display/dc/inc/hw/mcif_wb.h index 15cb782f129b..aae0ad7ae835 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/mcif_wb.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/mcif_wb.h @@ -27,6 +27,7 @@ #include "dc_hw_types.h" +#include "dml2_0/dml21/inc/dml_top_dchub_registers.h" enum mmhubbub_wbif_mode { PACKED_444 = 0, @@ -36,14 +37,21 @@ enum mmhubbub_wbif_mode { }; struct mcif_arb_params { - - unsigned int time_per_pixel; - unsigned int cli_watermark[4]; - unsigned int pstate_watermark[4]; - unsigned int arbitration_slice; - unsigned int slice_lines; - unsigned int max_scaled_time; - unsigned int dram_speed_change_duration; + union { + struct { + unsigned int time_per_pixel; + unsigned int cli_watermark[4]; + unsigned int pstate_watermark[4]; + unsigned int arbitration_slice; + unsigned int slice_lines; + unsigned int max_scaled_time; + unsigned int dram_speed_change_duration; + }; + struct { + struct dml2_mcif_global_register_set global_regs; + struct dml2_mcif_per_pipe_register_set inst_regs; + } dcn4x; //dcn4+ + }; }; struct mcif_irq_params { -- cgit v1.2.3 From 4d6eabd9bf7cbc96968726b7197cf5189a9dfe89 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 7 Jul 2026 10:21:50 -0400 Subject: drm/amd/display: Replace amdgpu_dm_kunit_helpers.h with dm_helpers.h Drop the amdgpu_dm_kunit_helpers.h include across the amdgpu_dm source files and use dm_helpers.h instead Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 - .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c | 2 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 2 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c | 2 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 1 - .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 1 - .../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 1 - .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c | 2 +- .../amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h | 20 -------------------- .../drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 1 - .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c | 2 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c | 2 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_services.c | 2 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c | 2 +- drivers/gpu/drm/amd/display/dc/dm_helpers.h | 11 +++++++++++ drivers/gpu/drm/amd/display/dc/os_types.h | 10 ++++++++++ 24 files changed, 37 insertions(+), 41 deletions(-) delete mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index e7080880b221..b1b107907066 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -70,7 +70,6 @@ #include "amdgpu_dm_audio.h" #include "amdgpu_dm_dmub.h" #include "amdgpu_dm_connector.h" -#include "amdgpu_dm_kunit_helpers.h" #include "ivsrcid/ivsrcid_vislands30.h" diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c index e97ce5c2c0e0..4b10c683df8e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c @@ -26,7 +26,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_audio.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "dc.h" #include diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 4eaf5e303850..b66ca60e697d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -47,7 +47,7 @@ #include "amdgpu_dm_trace.h" #include "amd_shared.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, int bl_idx) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 357c7c5c85cf..ab6e0cfb1572 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -33,7 +33,7 @@ #include "amdgpu_dm_colorop.h" #include "dc.h" #include "modules/color/color_gamma.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" /** diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c index 056a76b88f43..fae9007b9998 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c @@ -31,7 +31,7 @@ #include "amdgpu.h" #include "amdgpu_dm_colorop.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "dc.h" const u64 amdgpu_dm_supported_degam_tfs = diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 5c5f9c3e6d77..1ab372001524 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -42,7 +42,6 @@ #include "amdgpu_display.h" #include "amdgpu_dm.h" #include "amdgpu_dm_connector.h" -#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_plane.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_wb.h" diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index 970490c401e9..bd7444b05d6d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -33,7 +33,7 @@ #include "amdgpu_securedisplay.h" #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" static const char *const pipe_crc_sources[] = { "none", diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index d63688b9d93d..05d6915f9a6b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -34,7 +34,7 @@ #include "amdgpu_dm_plane.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_debugfs.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "modules/inc/mod_power.h" #define HPD_DETECTION_PERIOD_uS 2000000 diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 06f6915c7ca3..8e35d9ec22d6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -38,7 +38,7 @@ #include "amdgpu_ucode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_dmub.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include #include diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 5f43f6a74ffd..0dee603ecfaa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -31,7 +31,6 @@ #include "dm_helpers.h" #include #include "hdcp_psp.h" -#include "amdgpu_dm_kunit_helpers.h" /* * If the SRM version being loaded is less than or equal to the diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 30ac1f32fad5..7413da51415d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -48,7 +48,6 @@ #include "dm_helpers.h" #include "ddc_service_types.h" #include "clk_mgr.h" -#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_helpers.h" #define MCCS_DEST_ADDR (0x6E >> 1) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index ae3f81f53074..9c574849142b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -33,7 +33,7 @@ #include "amdgpu_display.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_hdcp.h" #include "amdgpu_dm_mst_types.h" diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c index a9575bf25fc2..4e57572e12b6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c @@ -32,7 +32,7 @@ #include "amdgpu_dm_ism.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_trace.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" /** diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h deleted file mode 100644 index 1f910a6a00c0..000000000000 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* - * Copyright 2026 Advanced Micro Devices, Inc. - */ - -#ifndef AMDGPU_DM_KUNIT_HELPERS_H -#define AMDGPU_DM_KUNIT_HELPERS_H - -#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) -#define STATIC_IFN_KUNIT -#define INLINE_IFN_KUNIT inline -#define EXPORT_IF_KUNIT(symbol) EXPORT_SYMBOL(symbol) - -#else -#define STATIC_IFN_KUNIT static -#define INLINE_IFN_KUNIT -#define EXPORT_IF_KUNIT(symbol) -#endif - -#endif /* AMDGPU_DM_KUNIT_HELPERS_H */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index a45d8d79f3b2..e001d868db80 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -40,7 +40,6 @@ #include "dc.h" #include "dm_helpers.h" -#include "amdgpu_dm_kunit_helpers.h" #include "ddc_service_types.h" #include "dpcd_defs.h" diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index 26f35434a92f..e43fce16b9ff 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -37,7 +37,7 @@ #include "amdgpu_display.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_plane.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "amdgpu_dm_colorop.h" #include "gc/gc_11_0_0_offset.h" #include "gc/gc_11_0_0_sh_mask.h" diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index 0d2e5294d062..8431e164a0db 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -33,7 +33,7 @@ #include "amdgpu_dm_irq.h" #include "amdgpu_pm.h" #include "dm_pp_smu.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "amdgpu_dm_pp_smu.h" STATIC_IFN_KUNIT void build_pm_display_cfg( diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c index f87de3d18ac0..dbad0a6d3a2b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c @@ -30,7 +30,7 @@ #include "dc.h" #include "amdgpu_dm.h" #include "modules/power/power_helpers.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" STATIC_IFN_KUNIT bool link_supports_psrsu(struct dc_link *link) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c index cf28d50c3b5e..0a7602ed70b2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c @@ -28,7 +28,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" struct amdgpu_dm_quirks { bool aux_hpd_discon; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c index 42e17119461d..ee2e4754b089 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c @@ -31,7 +31,7 @@ #include "modules/power/power_helpers.h" #include "dmub/inc/dmub_cmd.h" #include "dc/inc/link_service.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" /* diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c index 6c0464754ed8..dbdda6ed12d4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c @@ -36,7 +36,7 @@ #include "amdgpu_dm_irq.h" #include "amdgpu_pm.h" #include "amdgpu_dm_trace.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" unsigned long long dm_get_elapse_time_in_ns(struct dc_context *ctx, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c index 9e7bad4d6ed0..a7594012a0b2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c @@ -29,7 +29,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_wb.h" -#include "amdgpu_dm_kunit_helpers.h" +#include "dm_helpers.h" #include "amdgpu_display.h" #include "dc.h" diff --git a/drivers/gpu/drm/amd/display/dc/dm_helpers.h b/drivers/gpu/drm/amd/display/dc/dm_helpers.h index 63704d21a0b5..f5252931ce35 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_helpers.h +++ b/drivers/gpu/drm/amd/display/dc/dm_helpers.h @@ -192,6 +192,17 @@ void dm_helpers_mccs_vcp_set( struct dc_link *link, struct dc_sink *sink); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +#define STATIC_IFN_KUNIT +#define INLINE_IFN_KUNIT inline +#define EXPORT_IF_KUNIT(symbol) EXPORT_SYMBOL(symbol) + +#else +#define STATIC_IFN_KUNIT static +#define INLINE_IFN_KUNIT +#define EXPORT_IF_KUNIT(symbol) +#endif + bool dm_helpers_dp_handle_test_pattern_request( struct dc_context *ctx, const struct dc_link *link, diff --git a/drivers/gpu/drm/amd/display/dc/os_types.h b/drivers/gpu/drm/amd/display/dc/os_types.h index 6af831710489..538d00105738 100644 --- a/drivers/gpu/drm/amd/display/dc/os_types.h +++ b/drivers/gpu/drm/amd/display/dc/os_types.h @@ -57,6 +57,16 @@ #include "amdgpu_dm/dc_fpu.h" #endif /* CONFIG_DRM_AMD_DC_FP */ + +/* + * On Linux this is provided by and evaluates Kconfig + * options for both built-in (=y) and module (=m) cases. Windows has no + * Kconfig, so config options are never set here and this always yields 0. + */ +#ifndef IS_ENABLED +#define IS_ENABLED(option) 0 +#endif + /* * * general debug capabilities -- cgit v1.2.3 From 128729b7079df0b604d940bbb60b118bfe46eee0 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Wed, 24 Jun 2026 15:07:08 -0400 Subject: drm/amd/display: Add stream creation tests for connector Add KUnit coverage for create_stream_for_sink(): fake sink success, dm context setup, virtual signal handling, scaling source, and reuse of an existing sink. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 3 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 6 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 176 +++++++++++++++++++++ drivers/gpu/drm/amd/display/dc/core/dc_sink.c | 2 + drivers/gpu/drm/amd/display/dc/core/dc_stream.c | 2 + 5 files changed, 188 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 1ab372001524..43dbd524aa83 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1383,7 +1383,7 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, } #endif -static struct dc_stream_state * +STATIC_IFN_KUNIT struct dc_stream_state * create_stream_for_sink(struct drm_connector *connector, const struct drm_display_mode *drm_mode, const struct dm_connector_state *dm_state, @@ -1567,6 +1567,7 @@ finish: return stream; } +EXPORT_IF_KUNIT(create_stream_for_sink); /** * amdgpu_dm_connector_poll - Poll a connector to see if it's connected to a display diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 60f7ab63b782..0f28165854c8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -158,6 +158,12 @@ void fill_stream_properties_from_drm_display_mode( const struct drm_connector_state *connector_state, const struct dc_stream_state *old_stream, int requested_bpc); +struct dc_stream_state * +create_stream_for_sink(struct drm_connector *connector, + const struct drm_display_mode *drm_mode, + const struct dm_connector_state *dm_state, + const struct dc_stream_state *old_stream, + int requested_bpc); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 0a5d439f66c3..cba755f76cfa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -3604,6 +3604,176 @@ static void dm_test_fill_stream_aspect_ratio(struct kunit *test) (int)ASPECT_RATIO_16_9); } +/* Tests for create_stream_for_sink() */ + +/* + * Build the inputs for create_stream_for_sink(). The connector is registered + * against a real kunit drm_device so that to_amdgpu_dm_connector() and the drm + * debug helpers resolve. The DC link carries a zeroed dc_context so that + * dc_create_stream_for_sink() can allocate and construct a stream. + * + * By default no dc_sink is attached, so create_stream_for_sink() builds a fake + * VIRTUAL sink. The VIRTUAL signal keeps the DSC, audio and DP/HDMI infoframe + * paths as no-ops, making the exercised behaviour deterministic. + */ +struct dm_test_stream_ctx { + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_context *dc_ctx; + struct dc_link *link; + struct dm_connector_state *dm_state; + struct drm_display_mode *mode; +}; + +static struct dm_test_stream_ctx *dm_test_stream_ctx_alloc(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*ctx->drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(ctx->drm, &ctx->aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL), 0); + + ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx); + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->link->ctx = ctx->dc_ctx; + ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + ctx->aconnector->dc_link = ctx->link; + ctx->aconnector->dc_sink = NULL; + + ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + ctx->dm_state->scaling = RMX_OFF; + + ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->mode); + ctx->mode->hdisplay = 1920; + ctx->mode->vdisplay = 1080; + ctx->mode->clock = 148500; + + return ctx; +} + +/** + * dm_test_create_stream_fake_sink_success - Test a stream is built from a fake sink + * @test: The KUnit test context + */ +static void dm_test_create_stream_fake_sink_success(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test); + struct dc_stream_state *stream; + + stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, + ctx->dm_state, NULL, 8); + + KUNIT_ASSERT_NOT_NULL(test, stream); + dc_stream_release(stream); +} + +/** + * dm_test_create_stream_sets_dm_context - Test dm_stream_context points to aconnector + * @test: The KUnit test context + */ +static void dm_test_create_stream_sets_dm_context(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test); + struct dc_stream_state *stream; + + stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, + ctx->dm_state, NULL, 8); + + KUNIT_ASSERT_NOT_NULL(test, stream); + KUNIT_EXPECT_PTR_EQ(test, stream->dm_stream_context, ctx->aconnector); + dc_stream_release(stream); +} + +/** + * dm_test_create_stream_virtual_signal - Test the fake sink yields a VIRTUAL signal + * @test: The KUnit test context + */ +static void dm_test_create_stream_virtual_signal(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test); + struct dc_stream_state *stream; + + stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, + ctx->dm_state, NULL, 8); + + KUNIT_ASSERT_NOT_NULL(test, stream); + KUNIT_EXPECT_EQ(test, (int)stream->signal, (int)SIGNAL_TYPE_VIRTUAL); + dc_stream_release(stream); +} + +/** + * dm_test_create_stream_scaling_src - Test the source rect follows the mode + * @test: The KUnit test context + * + * With scaling off the full-screen source viewport matches the requested mode. + */ +static void dm_test_create_stream_scaling_src(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test); + struct dc_stream_state *stream; + + stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, + ctx->dm_state, NULL, 8); + + KUNIT_ASSERT_NOT_NULL(test, stream); + KUNIT_EXPECT_EQ(test, (int)stream->src.width, 1920); + KUNIT_EXPECT_EQ(test, (int)stream->src.height, 1080); + dc_stream_release(stream); +} + +/** + * dm_test_create_stream_existing_sink - Test the existing-sink retain path + * @test: The KUnit test context + * + * When the connector already has a dc_sink, create_stream_for_sink() reuses it + * instead of building a fake sink. + */ +static void dm_test_create_stream_existing_sink(struct kunit *test) +{ + struct dm_test_stream_ctx *ctx = dm_test_stream_ctx_alloc(test); + struct dc_sink_init_data sink_init = { 0 }; + struct dc_stream_state *stream; + struct dc_sink *sink; + + sink_init.link = ctx->link; + sink_init.sink_signal = SIGNAL_TYPE_VIRTUAL; + sink = dc_sink_create(&sink_init); + KUNIT_ASSERT_NOT_NULL(test, sink); + sink->sink_signal = SIGNAL_TYPE_VIRTUAL; + + ctx->aconnector->dc_sink = sink; + + stream = create_stream_for_sink(&ctx->aconnector->base, ctx->mode, + ctx->dm_state, NULL, 8); + + KUNIT_ASSERT_NOT_NULL(test, stream); + KUNIT_EXPECT_PTR_EQ(test, stream->sink, sink); + + dc_stream_release(stream); + dc_sink_release(sink); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -3798,6 +3968,12 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_fill_stream_color_depth_requested_bpc), KUNIT_CASE(dm_test_fill_stream_content_type), KUNIT_CASE(dm_test_fill_stream_aspect_ratio), + /* create_stream_for_sink */ + KUNIT_CASE(dm_test_create_stream_fake_sink_success), + KUNIT_CASE(dm_test_create_stream_sets_dm_context), + KUNIT_CASE(dm_test_create_stream_virtual_signal), + KUNIT_CASE(dm_test_create_stream_scaling_src), + KUNIT_CASE(dm_test_create_stream_existing_sink), {} }; diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_sink.c b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c index e5ac7056a187..21a988561496 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_sink.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c @@ -73,6 +73,7 @@ void dc_sink_release(struct dc_sink *sink) { kref_put(&sink->refcount, dc_sink_free); } +EXPORT_IF_KUNIT(dc_sink_release); struct dc_sink *dc_sink_create(const struct dc_sink_init_data *init_params) { @@ -94,6 +95,7 @@ construct_fail: alloc_fail: return NULL; } +EXPORT_IF_KUNIT(dc_sink_create); /******************************************************************************* * Protected functions - visible only inside of DC (not visible in DM) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c index dbc12640b01c..5a46d91e2f37 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c @@ -24,6 +24,7 @@ */ #include "dm_services.h" +#include "dm_helpers.h" #include "basics/dc_common.h" #include "dc.h" #include "core_types.h" @@ -203,6 +204,7 @@ void dc_stream_release(struct dc_stream_state *stream) kref_put(&stream->refcount, dc_stream_free); } } +EXPORT_IF_KUNIT(dc_stream_release); struct dc_stream_state *dc_create_stream_for_sink( struct dc_sink *sink) -- cgit v1.2.3 From ab9fb2738088cd8c16ac32e834d3ac929afe25ab Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Wed, 24 Jun 2026 15:21:18 -0400 Subject: drm/amd/display: Add detect and poll tests for connector Add KUnit coverage for amdgpu_dm_connector_detect() and amdgpu_dm_connector_poll(): force on (analog/digital), force off, sink present/absent, and the DAC-load cached status path. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 6 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 4 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 157 +++++++++++++++++++++ 3 files changed, 165 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 43dbd524aa83..4004cfbcc87f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1583,7 +1583,7 @@ EXPORT_IF_KUNIT(create_stream_for_sink); * * Return: The probed connector status (connected/disconnected/unknown). */ -static enum drm_connector_status +STATIC_IFN_KUNIT enum drm_connector_status amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) { struct drm_connector *connector = &aconnector->base; @@ -1635,6 +1635,7 @@ amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) mutex_unlock(&aconnector->hpd_lock); return status; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_poll); /** * amdgpu_dm_connector_detect() - Detect whether a DRM connector is connected to a display @@ -1658,7 +1659,7 @@ amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) * Return: The connector status (connected, disconnected, or unknown). * */ -static enum drm_connector_status +STATIC_IFN_KUNIT enum drm_connector_status amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); @@ -1682,6 +1683,7 @@ amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) return (aconnector->dc_sink ? connector_status_connected : connector_status_disconnected); } +EXPORT_IF_KUNIT(amdgpu_dm_connector_detect); int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, struct drm_connector_state *connector_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 0f28165854c8..5c48858f715e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -164,6 +164,10 @@ create_stream_for_sink(struct drm_connector *connector, const struct dm_connector_state *dm_state, const struct dc_stream_state *old_stream, int requested_bpc); +enum drm_connector_status +amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force); +enum drm_connector_status +amdgpu_dm_connector_detect(struct drm_connector *connector, bool force); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index cba755f76cfa..83afd917d605 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -3774,6 +3774,155 @@ static void dm_test_create_stream_existing_sink(struct kunit *test) dc_sink_release(sink); } +/* Tests for amdgpu_dm_connector_detect() */ + +/* + * A non-DisplayPort connector keeps update_subconnector_property() a no-op and, + * because the kunit thread is not the poll worker, the analog poll branch is + * skipped. That leaves the forced-state and dc_sink presence branches as the + * deterministic behaviour to exercise. + */ +static struct amdgpu_dm_connector *dm_test_detect_connector(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + + return dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); +} + +/** + * dm_test_detect_force_on - Test DRM_FORCE_ON reports connected + * @test: The KUnit test context + */ +static void dm_test_detect_force_on(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test); + + aconnector->base.force = DRM_FORCE_ON; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_detect(&aconnector->base, false), + (int)connector_status_connected); +} + +/** + * dm_test_detect_force_on_digital - Test DRM_FORCE_ON_DIGITAL reports connected + * @test: The KUnit test context + */ +static void dm_test_detect_force_on_digital(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test); + + aconnector->base.force = DRM_FORCE_ON_DIGITAL; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_detect(&aconnector->base, false), + (int)connector_status_connected); +} + +/** + * dm_test_detect_force_off - Test DRM_FORCE_OFF reports disconnected + * @test: The KUnit test context + */ +static void dm_test_detect_force_off(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test); + + aconnector->base.force = DRM_FORCE_OFF; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_detect(&aconnector->base, false), + (int)connector_status_disconnected); +} + +/** + * dm_test_detect_sink_present - Test a present dc_sink reports connected + * @test: The KUnit test context + */ +static void dm_test_detect_sink_present(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test); + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + aconnector->base.force = DRM_FORCE_UNSPECIFIED; + aconnector->dc_sink = sink; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_detect(&aconnector->base, false), + (int)connector_status_connected); +} + +/** + * dm_test_detect_no_sink - Test a missing dc_sink reports disconnected + * @test: The KUnit test context + */ +static void dm_test_detect_no_sink(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_detect_connector(test); + + aconnector->base.force = DRM_FORCE_UNSPECIFIED; + aconnector->dc_sink = NULL; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_detect(&aconnector->base, false), + (int)connector_status_disconnected); +} + +/* Tests for amdgpu_dm_connector_poll() */ + +/** + * dm_test_poll_dac_load_returns_cached - Test the DAC load detection shortcut + * @test: The KUnit test context + * + * When the previous connection was established by analog DAC load detection and + * polling is not forced, the connector is not re-detected and its cached status + * is returned unchanged. The connector is embedded in an amdgpu_device so that + * drm_to_adev() resolves. + */ +static void dm_test_poll_dac_load_returns_cached(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + struct dc_sink *local_sink; + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + adev = drm_to_adev(drm); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(drm, &aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_VGA, NULL), 0); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + local_sink = kunit_kzalloc(test, sizeof(*local_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, local_sink); + + link->local_sink = local_sink; + link->type = dc_connection_analog_load; + aconnector->dc_link = link; + + /* The cached status that the shortcut must return unchanged. */ + aconnector->base.status = connector_status_connected; + + KUNIT_EXPECT_EQ(test, + (int)amdgpu_dm_connector_poll(aconnector, false), + (int)connector_status_connected); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -3974,6 +4123,14 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_create_stream_virtual_signal), KUNIT_CASE(dm_test_create_stream_scaling_src), KUNIT_CASE(dm_test_create_stream_existing_sink), + /* amdgpu_dm_connector_detect */ + KUNIT_CASE(dm_test_detect_force_on), + KUNIT_CASE(dm_test_detect_force_on_digital), + KUNIT_CASE(dm_test_detect_force_off), + KUNIT_CASE(dm_test_detect_sink_present), + KUNIT_CASE(dm_test_detect_no_sink), + /* amdgpu_dm_connector_poll */ + KUNIT_CASE(dm_test_poll_dac_load_returns_cached), {} }; -- cgit v1.2.3 From 663c4a821613f2758b0810253137d293c7222d3c Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Wed, 24 Jun 2026 15:35:51 -0400 Subject: drm/amd/display: Add register and unregister tests for connector Add KUnit coverage for amdgpu_dm_connector_late_register() and amdgpu_dm_connector_unregister(): non-DP late register succeeds and non-DP unregister is a no-op. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 6 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 2 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 70 ++++++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 4004cfbcc87f..fa2d451cf2a7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1802,7 +1802,7 @@ int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, } EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_get_property); -static void amdgpu_dm_connector_unregister(struct drm_connector *connector) +STATIC_IFN_KUNIT void amdgpu_dm_connector_unregister(struct drm_connector *connector) { struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); @@ -1812,6 +1812,7 @@ static void amdgpu_dm_connector_unregister(struct drm_connector *connector) cec_notifier_conn_unregister(amdgpu_dm_connector->notifier); drm_dp_aux_unregister(&amdgpu_dm_connector->dm_dp_aux.aux); } +EXPORT_IF_KUNIT(amdgpu_dm_connector_unregister); static void amdgpu_dm_connector_destroy(struct drm_connector *connector) { @@ -1915,7 +1916,7 @@ amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector) } EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_duplicate_state); -static int +STATIC_IFN_KUNIT int amdgpu_dm_connector_late_register(struct drm_connector *connector) { struct amdgpu_dm_connector *amdgpu_dm_connector = @@ -1945,6 +1946,7 @@ amdgpu_dm_connector_late_register(struct drm_connector *connector) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_late_register); static void amdgpu_dm_connector_funcs_force(struct drm_connector *connector) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 5c48858f715e..a535511acebb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -168,6 +168,8 @@ enum drm_connector_status amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force); enum drm_connector_status amdgpu_dm_connector_detect(struct drm_connector *connector, bool force); +void amdgpu_dm_connector_unregister(struct drm_connector *connector); +int amdgpu_dm_connector_late_register(struct drm_connector *connector); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 83afd917d605..dc40bb5c7568 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -3923,6 +3923,72 @@ static void dm_test_poll_dac_load_returns_cached(struct kunit *test) (int)connector_status_connected); } +/* Tests for amdgpu_dm_connector_late_register() and _unregister() */ + +/* + * Build an amdgpu_dm_connector embedded in an amdgpu_device so drm_to_adev() + * resolves. A VGA connector keeps amdgpu_dm_should_create_sysfs() false (sysfs + * and DP AUX branches skipped) and bl_idx == -1 turns backlight registration + * into a no-op, leaving the register/unregister bookkeeping safe to exercise. + */ +static struct amdgpu_dm_connector *dm_test_reg_connector(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_EQ(test, + drmm_connector_init(drm, &aconnector->base, + &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_VGA, NULL), 0); + + aconnector->bl_idx = -1; + + return aconnector; +} + +/** + * dm_test_late_register_non_dp_succeeds - Test late_register on a plain connector + * @test: The KUnit test context + * + * With sysfs, backlight and DP AUX registration all skipped, late_register + * completes successfully. + */ +static void dm_test_late_register_non_dp_succeeds(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_reg_connector(test); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_connector_late_register(&aconnector->base), 0); +} + +/** + * dm_test_unregister_non_dp_noop - Test unregister tolerates an unregistered connector + * @test: The KUnit test context + * + * No sysfs group was created, the CEC notifier is NULL and the DP AUX channel + * was never registered, so unregister must be a safe no-op. + */ +static void dm_test_unregister_non_dp_noop(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = dm_test_reg_connector(test); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(aconnector)); + + amdgpu_dm_connector_unregister(&aconnector->base); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -4131,6 +4197,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_detect_no_sink), /* amdgpu_dm_connector_poll */ KUNIT_CASE(dm_test_poll_dac_load_returns_cached), + /* amdgpu_dm_connector_late_register */ + KUNIT_CASE(dm_test_late_register_non_dp_succeeds), + /* amdgpu_dm_connector_unregister */ + KUNIT_CASE(dm_test_unregister_non_dp_noop), {} }; -- cgit v1.2.3 From e1b6b01c9223668657ac167117c293b74e9f7857 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Wed, 24 Jun 2026 15:58:07 -0400 Subject: drm/amd/display: Add destroy tests for connector Add KUnit coverage for amdgpu_dm_connector_destroy(): minimal teardown plus releasing the dc_sink and dc_em_sink references. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 3 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 1 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 149 +++++++++++++++++++++ drivers/gpu/drm/amd/display/dc/core/dc_sink.c | 1 + 4 files changed, 153 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index fa2d451cf2a7..4cb73cb1a6fe 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1814,7 +1814,7 @@ STATIC_IFN_KUNIT void amdgpu_dm_connector_unregister(struct drm_connector *conne } EXPORT_IF_KUNIT(amdgpu_dm_connector_unregister); -static void amdgpu_dm_connector_destroy(struct drm_connector *connector) +STATIC_IFN_KUNIT void amdgpu_dm_connector_destroy(struct drm_connector *connector) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); struct amdgpu_device *adev = drm_to_adev(connector->dev); @@ -1855,6 +1855,7 @@ static void amdgpu_dm_connector_destroy(struct drm_connector *connector) kfree(connector); } +EXPORT_IF_KUNIT(amdgpu_dm_connector_destroy); void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index a535511acebb..e1203ab3e11a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -170,6 +170,7 @@ enum drm_connector_status amdgpu_dm_connector_detect(struct drm_connector *connector, bool force); void amdgpu_dm_connector_unregister(struct drm_connector *connector); int amdgpu_dm_connector_late_register(struct drm_connector *connector); +void amdgpu_dm_connector_destroy(struct drm_connector *connector); enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state); bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index dc40bb5c7568..0a08ec6ed388 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -3989,6 +3989,151 @@ static void dm_test_unregister_non_dp_noop(struct kunit *test) amdgpu_dm_connector_unregister(&aconnector->base); } +/* Tests for amdgpu_dm_connector_destroy() */ + +/* + * amdgpu_dm_connector_destroy() ends with drm_connector_cleanup() followed by + * kfree(connector), so the connector must be initialised with the unmanaged + * drm_connector_init() and allocated with kzalloc() (the function frees it, so + * kunit_kzalloc() would double free at teardown). It is embedded in an + * amdgpu_device so drm_to_adev() resolves and a dc_link carries a dc_context so + * dc_sink_create() works for the sink-release branches. + */ +struct dm_test_destroy_ctx { + struct drm_device *drm; + struct dc_context *dc_ctx; + struct dc_link *link; +}; + +static struct dm_test_destroy_ctx *dm_test_destroy_ctx_alloc(struct kunit *test) +{ + struct dm_test_destroy_ctx *ctx; + struct amdgpu_device *adev; + struct device *dev; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + ctx->drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm); + + ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx); + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->link->ctx = ctx->dc_ctx; + + return ctx; +} + +/* + * Allocate a connector the destroy path can free. Uses kzalloc() (not + * kunit_kzalloc) and the unmanaged drm_connector_init() because the function + * under test calls drm_connector_cleanup() + kfree(connector). + * + * drm_connector_init() requires funcs->destroy to be set, so a dedicated funcs + * table wires it to amdgpu_dm_connector_destroy() (the test invokes it + * directly; the connector is removed from the device before teardown). + */ +static const struct drm_connector_funcs dm_test_destroy_funcs = { + .reset = amdgpu_dm_connector_funcs_reset, + .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .destroy = amdgpu_dm_connector_destroy, +}; + +static struct amdgpu_dm_connector * +dm_test_destroy_connector(struct kunit *test, struct drm_device *drm) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kzalloc(sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + KUNIT_ASSERT_EQ(test, + drm_connector_init(drm, &aconnector->base, + &dm_test_destroy_funcs, + DRM_MODE_CONNECTOR_VGA), 0); + aconnector->bl_idx = -1; + + return aconnector; +} + +/** + * dm_test_destroy_minimal - Test destroy tears down a bare connector + * @test: The KUnit test context + * + * With no MST, backlight, sinks or registered AUX/CEC, destroy must clean up + * and free the connector without crashing. + */ +static void dm_test_destroy_minimal(struct kunit *test) +{ + struct dm_test_destroy_ctx *ctx = dm_test_destroy_ctx_alloc(test); + struct amdgpu_dm_connector *aconnector = + dm_test_destroy_connector(test, ctx->drm); + + amdgpu_dm_connector_destroy(&aconnector->base); +} + +/** + * dm_test_destroy_releases_dc_sink - Test destroy releases the dc_sink + * @test: The KUnit test context + */ +static void dm_test_destroy_releases_dc_sink(struct kunit *test) +{ + struct dm_test_destroy_ctx *ctx = dm_test_destroy_ctx_alloc(test); + struct amdgpu_dm_connector *aconnector = + dm_test_destroy_connector(test, ctx->drm); + struct dc_sink_init_data sink_init = { 0 }; + struct dc_sink *sink; + + sink_init.link = ctx->link; + sink_init.sink_signal = SIGNAL_TYPE_VIRTUAL; + sink = dc_sink_create(&sink_init); + KUNIT_ASSERT_NOT_NULL(test, sink); + + /* Extra reference so the sink survives destroy for inspection. */ + dc_sink_retain(sink); + aconnector->dc_sink = sink; + + amdgpu_dm_connector_destroy(&aconnector->base); + + KUNIT_EXPECT_EQ(test, (int)kref_read(&sink->refcount), 1); + dc_sink_release(sink); +} + +/** + * dm_test_destroy_releases_dc_em_sink - Test destroy releases the emulated sink + * @test: The KUnit test context + */ +static void dm_test_destroy_releases_dc_em_sink(struct kunit *test) +{ + struct dm_test_destroy_ctx *ctx = dm_test_destroy_ctx_alloc(test); + struct amdgpu_dm_connector *aconnector = + dm_test_destroy_connector(test, ctx->drm); + struct dc_sink_init_data sink_init = { 0 }; + struct dc_sink *sink; + + sink_init.link = ctx->link; + sink_init.sink_signal = SIGNAL_TYPE_VIRTUAL; + sink = dc_sink_create(&sink_init); + KUNIT_ASSERT_NOT_NULL(test, sink); + + dc_sink_retain(sink); + aconnector->dc_em_sink = sink; + + amdgpu_dm_connector_destroy(&aconnector->base); + + KUNIT_EXPECT_EQ(test, (int)kref_read(&sink->refcount), 1); + dc_sink_release(sink); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -4201,6 +4346,10 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_late_register_non_dp_succeeds), /* amdgpu_dm_connector_unregister */ KUNIT_CASE(dm_test_unregister_non_dp_noop), + /* amdgpu_dm_connector_destroy */ + KUNIT_CASE(dm_test_destroy_minimal), + KUNIT_CASE(dm_test_destroy_releases_dc_sink), + KUNIT_CASE(dm_test_destroy_releases_dc_em_sink), {} }; diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_sink.c b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c index 21a988561496..38ef295e43d6 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_sink.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_sink.c @@ -61,6 +61,7 @@ void dc_sink_retain(struct dc_sink *sink) { kref_get(&sink->refcount); } +EXPORT_IF_KUNIT(dc_sink_retain); static void dc_sink_free(struct kref *kref) { -- cgit v1.2.3 From 1f235b05f6e54d16c5ae884d2b93f58c87b95f11 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Wed, 24 Jun 2026 16:19:32 -0400 Subject: drm/amd/display: Add encoder helper tests for connector Add KUnit coverage for the encoder helpers dm_encoder_helper_disable() and dm_encoder_helper_atomic_check(): disable no-op, eDP native keeps scaling, LVDS non-native enables scaling, and the non-MST zero return. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 6 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 4 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 139 +++++++++++++++++++++ 3 files changed, 147 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 4cb73cb1a6fe..208adab6eefd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -90,11 +90,12 @@ static const struct drm_encoder_funcs amdgpu_dm_encoder_funcs = { .destroy = amdgpu_dm_encoder_destroy, }; -static void dm_encoder_helper_disable(struct drm_encoder *encoder) +STATIC_IFN_KUNIT void dm_encoder_helper_disable(struct drm_encoder *encoder) { } +EXPORT_IF_KUNIT(dm_encoder_helper_disable); -static int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, +STATIC_IFN_KUNIT int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { @@ -164,6 +165,7 @@ static int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, } return 0; } +EXPORT_IF_KUNIT(dm_encoder_helper_atomic_check); const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs = { .disable = dm_encoder_helper_disable, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index e1203ab3e11a..adf69de4e482 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -146,6 +146,10 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, uint32_t link_index); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void dm_encoder_helper_disable(struct drm_encoder *encoder); +int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state); enum drm_mode_subconnector get_subconnector_type(struct dc_link *link); void update_subconnector_property(struct amdgpu_dm_connector *aconnector); void amdgpu_dm_fbc_init(struct drm_connector *connector); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 0a08ec6ed388..ea3e18264495 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -4134,6 +4134,139 @@ static void dm_test_destroy_releases_dc_em_sink(struct kunit *test) dc_sink_release(sink); } +/* Tests for dm_encoder_helper_disable() */ + +/** + * dm_test_encoder_disable_noop - Test the disable hook is a no-op + * @test: The KUnit test context + * + * dm_encoder_helper_disable() has an empty body; calling it must neither touch + * the encoder nor crash. + */ +static void dm_test_encoder_disable_noop(struct kunit *test) +{ + struct drm_encoder *encoder; + + encoder = kunit_kzalloc(test, sizeof(*encoder), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, encoder); + + dm_encoder_helper_disable(encoder); +} + +/* Tests for dm_encoder_helper_atomic_check() */ + +/* + * dm_encoder_helper_atomic_check() reads back through to_amdgpu_encoder(), + * to_amdgpu_dm_connector() and to_dm_connector_state(), so the encoder, + * connector and connector-state are stacked in their containers and wired + * together through conn_state->connector. + */ +struct dm_test_atomic_check_ctx { + struct drm_device *drm; + struct amdgpu_encoder *aenc; + struct amdgpu_dm_connector *aconnector; + struct dm_connector_state *dm_state; + struct drm_crtc_state *crtc_state; +}; + +static struct dm_test_atomic_check_ctx * +dm_test_atomic_check_ctx_alloc(struct kunit *test, int connector_type) +{ + struct dm_test_atomic_check_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->drm = dm_test_alloc_drm(test); + + ctx->aenc = kunit_kzalloc(test, sizeof(*ctx->aenc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aenc); + ctx->aenc->base.dev = ctx->drm; + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->aconnector->base.connector_type = connector_type; + + ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + ctx->dm_state->base.connector = &ctx->aconnector->base; + + ctx->crtc_state = kunit_kzalloc(test, sizeof(*ctx->crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->crtc_state); + + return ctx; +} + +/** + * dm_test_atomic_check_edp_native_keeps_scaling - Test native eDP mode is left alone + * @test: The KUnit test context + * + * On an eDP connector whose adjusted mode matches the panel's native mode, + * drm_crtc_helper_mode_valid_fixed() returns MODE_OK so scaling is untouched. + */ +static void dm_test_atomic_check_edp_native_keeps_scaling(struct kunit *test) +{ + struct dm_test_atomic_check_ctx *ctx = + dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP); + + ctx->aenc->native_mode.hdisplay = 1920; + ctx->aenc->native_mode.vdisplay = 1080; + ctx->crtc_state->adjusted_mode.hdisplay = 1920; + ctx->crtc_state->adjusted_mode.vdisplay = 1080; + ctx->dm_state->scaling = RMX_OFF; + + KUNIT_EXPECT_EQ(test, + dm_encoder_helper_atomic_check(&ctx->aenc->base, + ctx->crtc_state, + &ctx->dm_state->base), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->dm_state->scaling, (int)RMX_OFF); +} + +/** + * dm_test_atomic_check_lvds_non_native_enables_scaling - Test non-native LVDS turns on scaling + * @test: The KUnit test context + * + * On an LVDS connector whose adjusted mode differs from the native mode and is + * currently RMX_OFF, the check enables RMX_ASPECT scaling and still returns 0. + */ +static void dm_test_atomic_check_lvds_non_native_enables_scaling(struct kunit *test) +{ + struct dm_test_atomic_check_ctx *ctx = + dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_LVDS); + + ctx->aenc->native_mode.hdisplay = 1920; + ctx->aenc->native_mode.vdisplay = 1080; + ctx->crtc_state->adjusted_mode.hdisplay = 1280; + ctx->crtc_state->adjusted_mode.vdisplay = 720; + ctx->dm_state->scaling = RMX_OFF; + + KUNIT_EXPECT_EQ(test, + dm_encoder_helper_atomic_check(&ctx->aenc->base, + ctx->crtc_state, + &ctx->dm_state->base), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->dm_state->scaling, (int)RMX_ASPECT); +} + +/** + * dm_test_atomic_check_non_mst_returns_zero - Test non-MST connectors short-circuit + * @test: The KUnit test context + * + * A non-eDP/LVDS connector with no MST output port hits the early ``return 0`` + * before any topology state is touched. + */ +static void dm_test_atomic_check_non_mst_returns_zero(struct kunit *test) +{ + struct dm_test_atomic_check_ctx *ctx = + dm_test_atomic_check_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA); + + ctx->aconnector->mst_output_port = NULL; + + KUNIT_EXPECT_EQ(test, + dm_encoder_helper_atomic_check(&ctx->aenc->base, + ctx->crtc_state, + &ctx->dm_state->base), 0); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -4350,6 +4483,12 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_destroy_minimal), KUNIT_CASE(dm_test_destroy_releases_dc_sink), KUNIT_CASE(dm_test_destroy_releases_dc_em_sink), + /* dm_encoder_helper_disable */ + KUNIT_CASE(dm_test_encoder_disable_noop), + /* dm_encoder_helper_atomic_check */ + KUNIT_CASE(dm_test_atomic_check_edp_native_keeps_scaling), + KUNIT_CASE(dm_test_atomic_check_lvds_non_native_enables_scaling), + KUNIT_CASE(dm_test_atomic_check_non_mst_returns_zero), {} }; -- cgit v1.2.3 From 27be8a5159b28067d1e037a1e5c5de81efef8535 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Wed, 24 Jun 2026 16:32:57 -0400 Subject: drm/amd/display: Add EDID management tests for connector Add KUnit coverage for hdmi_cec_unset_edid(), create_eml_sink() and handle_edid_mgmt(): unset edid with no notifier, eml sink with no edid, and DP vs non-DP edid management link caps handling. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 9 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 3 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 119 +++++++++++++++++++++ 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 208adab6eefd..6e07350b86ef 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -334,7 +334,7 @@ int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev) } EXPORT_IF_KUNIT(amdgpu_dm_detect_mst_link_for_all_connectors); -static void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) { struct cec_notifier *n = aconnector->notifier; @@ -343,6 +343,7 @@ static void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) cec_notifier_phys_addr_invalidate(n); } +EXPORT_IF_KUNIT(hdmi_cec_unset_edid); void amdgpu_dm_hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector) { @@ -2007,7 +2008,7 @@ static int get_modes(struct drm_connector *connector) return amdgpu_dm_connector_get_modes(connector); } -static void create_eml_sink(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void create_eml_sink(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; struct dc_link *dc_link = aconnector->dc_link; @@ -2052,8 +2053,9 @@ static void create_eml_sink(struct amdgpu_dm_connector *aconnector) dc_sink_retain(aconnector->dc_sink); } } +EXPORT_IF_KUNIT(create_eml_sink); -static void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) { struct dc_link *link = (struct dc_link *)aconnector->dc_link; @@ -2068,6 +2070,7 @@ static void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) create_eml_sink(aconnector); } +EXPORT_IF_KUNIT(handle_edid_mgmt); static enum dc_status dm_validate_stream_and_context(struct dc *dc, struct dc_stream_state *stream) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index adf69de4e482..1cce1e8863c6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -146,6 +146,9 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, uint32_t link_index); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector); +void create_eml_sink(struct amdgpu_dm_connector *aconnector); +void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector); void dm_encoder_helper_disable(struct drm_encoder *encoder); int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index ea3e18264495..e8335963050f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -4267,6 +4267,118 @@ static void dm_test_atomic_check_non_mst_returns_zero(struct kunit *test) &ctx->dm_state->base), 0); } +/* Tests for hdmi_cec_unset_edid() */ + +/** + * dm_test_hdmi_cec_unset_edid_no_notifier - Test the no-notifier no-op path + * @test: The KUnit test context + * + * With aconnector->notifier NULL the function returns early and must not crash. + */ +static void dm_test_hdmi_cec_unset_edid_no_notifier(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + hdmi_cec_unset_edid(aconnector); +} + +/* Tests for create_eml_sink() and handle_edid_mgmt() */ + +/* + * create_eml_sink() reads EDID off the connector's DDC. Forcing the connector + * DRM_FORCE_OFF makes drm_edid_read_ddc() return NULL before touching any i2c + * adapter, exercising the "no EDID" branch without real hardware. aux_mode is + * set so the embedded DP AUX ddc is selected (no i2c adapter pointer needed). + */ +struct dm_test_edid_ctx { + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; +}; + +static struct dm_test_edid_ctx * +dm_test_edid_ctx_alloc(struct kunit *test, int connector_type) +{ + struct dm_test_edid_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->drm = dm_test_alloc_drm(test); + ctx->aconnector = dm_test_add_connector(test, ctx->drm, connector_type); + + ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->link->aux_mode = true; + ctx->aconnector->dc_link = ctx->link; + + ctx->aconnector->base.force = DRM_FORCE_OFF; + + return ctx; +} + +/** + * dm_test_create_eml_sink_no_edid - Test the no-EDID branch creates no sink + * @test: The KUnit test context + * + * When no EDID can be read the function logs an error and returns without + * allocating an emulated sink. + */ +static void dm_test_create_eml_sink_no_edid(struct kunit *test) +{ + struct dm_test_edid_ctx *ctx = + dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort); + + create_eml_sink(ctx->aconnector); + + KUNIT_EXPECT_NULL(test, ctx->aconnector->dc_em_sink); +} + +/** + * dm_test_handle_edid_mgmt_dp_sets_link_caps - Test DP seeds verified link caps + * @test: The KUnit test context + * + * For a DisplayPort link the function primes verified_link_cap before reading + * EDID so a headless force-on connector can still modeset. + */ +static void dm_test_handle_edid_mgmt_dp_sets_link_caps(struct kunit *test) +{ + struct dm_test_edid_ctx *ctx = + dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort); + + ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + handle_edid_mgmt(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->verified_link_cap.lane_count, + (int)LANE_COUNT_FOUR); + KUNIT_EXPECT_EQ(test, (int)ctx->link->verified_link_cap.link_rate, + (int)LINK_RATE_HIGH2); + KUNIT_EXPECT_NULL(test, ctx->aconnector->dc_em_sink); +} + +/** + * dm_test_handle_edid_mgmt_non_dp_leaves_caps - Test non-DP links keep zeroed caps + * @test: The KUnit test context + * + * A non-DisplayPort link skips the verified_link_cap seeding entirely. + */ +static void dm_test_handle_edid_mgmt_non_dp_leaves_caps(struct kunit *test) +{ + struct dm_test_edid_ctx *ctx = + dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA); + + ctx->link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + + handle_edid_mgmt(ctx->aconnector); + + KUNIT_EXPECT_EQ(test, (int)ctx->link->verified_link_cap.lane_count, 0); + KUNIT_EXPECT_EQ(test, (int)ctx->link->verified_link_cap.link_rate, 0); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -4489,6 +4601,13 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_atomic_check_edp_native_keeps_scaling), KUNIT_CASE(dm_test_atomic_check_lvds_non_native_enables_scaling), KUNIT_CASE(dm_test_atomic_check_non_mst_returns_zero), + /* hdmi_cec_unset_edid */ + KUNIT_CASE(dm_test_hdmi_cec_unset_edid_no_notifier), + /* create_eml_sink */ + KUNIT_CASE(dm_test_create_eml_sink_no_edid), + /* handle_edid_mgmt */ + KUNIT_CASE(dm_test_handle_edid_mgmt_dp_sets_link_caps), + KUNIT_CASE(dm_test_handle_edid_mgmt_non_dp_leaves_caps), {} }; -- cgit v1.2.3 From 47c3b22a38ffec9110c466a72965218b6ebe79fe Mon Sep 17 00:00:00 2001 From: Robin Chen Date: Wed, 8 Jul 2026 21:00:42 +0800 Subject: drm/amd/display: fix debug flags assignment in dmub_replay.c [WHY] Fix incorrect casting of debug flags to uint16_t, which could truncate the value. Reviewed-by: ChunTao Tso Signed-off-by: Robin Chen Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dce/dmub_replay.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_replay.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_replay.c index 9b50bf1d80fc..eda3a2f1402c 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dmub_replay.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_replay.c @@ -165,7 +165,7 @@ static bool dmub_replay_copy_settings(struct dmub_replay *dmub, // Misc copy_settings_data->line_time_in_ns = (uint16_t)replay_context->line_time_in_ns; copy_settings_data->panel_inst = (uint16_t)panel_inst; - copy_settings_data->debug.u32All = (uint16_t)link->replay_settings.config.debug_flags; + copy_settings_data->debug.u32All = link->replay_settings.config.debug_flags; copy_settings_data->pixel_deviation_per_line = link->dpcd_caps.pr_info.pixel_deviation_per_line; copy_settings_data->max_deviation_line = (uint16_t)link->dpcd_caps.pr_info.max_deviation_line; copy_settings_data->smu_optimizations_en = link->replay_settings.replay_smu_opt_enable; -- cgit v1.2.3 From cfc867ccfabd29c2ed586dbd906c0f07a5dc28e8 Mon Sep 17 00:00:00 2001 From: Dillon Varone Date: Thu, 26 Mar 2026 14:30:36 -0400 Subject: drm/amd/display: Add DWB validation support to DML2.1 wrapper [WHY&HOW] DML2.1 wrapper was lacking translation for DWB. This change adds the necessary translation for DWB validation and programming support. Reviewed-by: Austin Zheng Signed-off-by: Dillon Varone Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../dc/dml2_0/dml21/dml21_translation_helper.c | 56 ++++++++++++++++++ .../drm/amd/display/dc/dml2_0/dml21/dml21_utils.c | 17 ++++++ .../drm/amd/display/dc/dml2_0/dml21/dml21_utils.h | 5 ++ .../display/dc/dml2_0/dml21/dml21_wrapper_fpu.c | 10 ++++ .../display/dc/dml2_0/dml21/inc/dml_top_types.h | 2 + .../dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c | 34 ++++++++++- .../dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 68 +++++++++++++++++++--- .../dml21/src/dml2_core/dml2_core_dcn4_calcs.h | 2 + .../dc/dml2_0/dml21/src/dml2_dpmm/dml2_dpmm_dcn4.c | 21 +++++++ 9 files changed, 206 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c index 51260369cd8a..a76dcde1efbf 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c @@ -267,6 +267,61 @@ static void populate_dml21_output_config_from_stream_state(struct dml2_link_outp // output->validate_output } +static void populate_dml21_writeback_config_from_stream_state(struct dml2_writeback_cfg *writeback, + const struct dc_stream_state *stream) +{ + if (stream->num_wb_info > 0) { + writeback->active_writebacks_per_stream = stream->num_wb_info <= DML2_MAX_WRITEBACK ? + stream->num_wb_info : DML2_MAX_WRITEBACK; + + ASSERT(stream->num_wb_info <= DML2_MAX_WRITEBACK); + + for (unsigned int wb_index = 0; wb_index < stream->num_wb_info; wb_index++) { + const struct dc_writeback_info *dc_wb_info = &stream->writeback_info[wb_index]; + struct dml2_writeback_info *wb_info = &writeback->writeback_stream[wb_index]; + + switch (dc_wb_info->dwb_params.cnv_params.fc_out_format) { + case DWB_OUT_FORMAT_64BPP_ARGB: + case DWB_OUT_FORMAT_64BPP_RGBA: + wb_info->pixel_format = dml2_444_64; + break; + case DWB_OUT_FORMAT_32BPP_ARGB: + case DWB_OUT_FORMAT_32BPP_RGBA: + default: + wb_info->pixel_format = dml2_444_32; + break; + } + + wb_info->input_width = dc_wb_info->dwb_params.cnv_params.crop_en ? + dc_wb_info->dwb_params.cnv_params.crop_width : + dc_wb_info->dwb_params.cnv_params.src_width; + wb_info->input_height = dc_wb_info->dwb_params.cnv_params.crop_en ? + dc_wb_info->dwb_params.cnv_params.crop_height : + dc_wb_info->dwb_params.cnv_params.src_height; + wb_info->output_width = dc_wb_info->dwb_params.dest_width; + wb_info->output_height = dc_wb_info->dwb_params.dest_height; + wb_info->v_taps = dc_wb_info->dwb_params.scaler_taps.v_taps > 0 ? + dc_wb_info->dwb_params.scaler_taps.v_taps : 1; + wb_info->h_taps = dc_wb_info->dwb_params.scaler_taps.h_taps > 0 ? + dc_wb_info->dwb_params.scaler_taps.h_taps : 1; + wb_info->v_taps_chroma = dc_wb_info->dwb_params.scaler_taps.v_taps_c > 0 ? + dc_wb_info->dwb_params.scaler_taps.v_taps_c : 1; + wb_info->h_taps_chroma = dc_wb_info->dwb_params.scaler_taps.h_taps_c > 0 ? + dc_wb_info->dwb_params.scaler_taps.h_taps_c : 1; + wb_info->h_ratio = dc_wb_info->dwb_params.cnv_params.crop_en ? + (double)dc_wb_info->dwb_params.cnv_params.crop_width / + (double)dc_wb_info->dwb_params.dest_width : + (double)dc_wb_info->dwb_params.cnv_params.src_width / + (double)dc_wb_info->dwb_params.dest_width; + wb_info->v_ratio = dc_wb_info->dwb_params.cnv_params.crop_en ? + (double)dc_wb_info->dwb_params.cnv_params.crop_height / + (double)dc_wb_info->dwb_params.dest_height : + (double)dc_wb_info->dwb_params.cnv_params.src_height / + (double)dc_wb_info->dwb_params.dest_height; + } + } +} + static void populate_dml21_stream_overrides_from_stream_state( struct dml2_stream_parameters *stream_desc, struct dc_stream_state *stream, @@ -826,6 +881,7 @@ bool dml21_map_dc_state_into_dml_display_cfg(const struct dc *in_dc, struct dc_s ASSERT(disp_cfg_stream_location >= 0 && disp_cfg_stream_location < __DML2_WRAPPER_MAX_STREAMS_PLANES__); populate_dml21_timing_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].timing, context->streams[stream_index], otg_master_pipe, dml_ctx); populate_dml21_output_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].output, context->streams[stream_index], otg_master_pipe); + populate_dml21_writeback_config_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location].writeback, context->streams[stream_index]); populate_dml21_stream_overrides_from_stream_state(&dml_dispcfg->stream_descriptors[disp_cfg_stream_location], context->streams[stream_index], &context->stream_status[stream_index]); dml_dispcfg->stream_descriptors[disp_cfg_stream_location].overrides.hw.twait_budgeting.fclk_pstate = dml2_twait_budgeting_setting_if_needed; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c index 835fece1d46a..16e1315da2fa 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c @@ -532,3 +532,20 @@ bool dml21_is_plane1_enabled(enum dml2_source_format_class source_format) { return source_format >= dml2_420_8 && source_format <= dml2_rgbe_alpha; } + +void dml21_program_dc_mcif_arb_params(struct dml2_context *dml_ctx, + struct dc_state *context, + struct dml2_per_stream_programming *stream_prog, + unsigned int wb_index, + unsigned int dwb_inst) +{ + /* DC struct contains global reg for every WB instance */ + memcpy(&context->bw_ctx.bw.dcn.bw_writeback.mcif_wb_arb[dwb_inst].dcn4x.global_regs, + &dml_ctx->v21.mode_programming.programming->mcif_global_regs, + sizeof(struct dml2_mcif_global_register_set)); + + /* copy per-DWB pipe registers */ + memcpy(&context->bw_ctx.bw.dcn.bw_writeback.mcif_wb_arb[dwb_inst].dcn4x.inst_regs, + stream_prog->mcif_regs[wb_index], + sizeof(struct dml2_mcif_per_pipe_register_set)); +} diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.h index bff945a4ab3a..7312c33143c6 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.h @@ -48,4 +48,9 @@ void dml21_build_fams2_programming(const struct dc *dc, struct dc_state *context, struct dml2_context *dml_ctx); bool dml21_is_plane1_enabled(enum dml2_source_format_class source_format); +void dml21_program_dc_mcif_arb_params(struct dml2_context *dml_ctx, + struct dc_state *context, + struct dml2_per_stream_programming *stream_prog, + unsigned int wb_index, + unsigned int dwb_inst); #endif diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c index 11fc0b1cd152..a975d36ce15d 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c @@ -60,6 +60,7 @@ static void dml21_calculate_rq_and_dlg_params(const struct dc *dc, struct dc_sta struct pipe_ctx *dc_phantom_pipes[__DML2_WRAPPER_MAX_STREAMS_PLANES__] = {0}; int num_pipes; unsigned int dml_phantom_prog_idx; + unsigned int stream_wb_idx; context->bw_ctx.bw.dcn.clk.dppclk_khz = 0; @@ -102,6 +103,15 @@ static void dml21_calculate_rq_and_dlg_params(const struct dc *dc, struct dc_sta } } + /* program WB */ + for (stream_wb_idx = 0; stream_wb_idx < stream_prog->stream_descriptor->writeback.active_writebacks_per_stream; stream_wb_idx++) { + dml21_program_dc_mcif_arb_params(in_ctx, + context, + stream_prog, + stream_wb_idx, + dc_main_pipes[0]->stream->writeback_info[stream_wb_idx].dwb_pipe_inst); + } + /* copy per plane mcache allocation */ memcpy(&context->bw_ctx.bw.dcn.mcache_allocations[dml_prog_idx], &pln_prog->mcache_allocation, sizeof(struct dml2_mcache_surface_allocation)); if (pln_prog->phantom_plane.valid) { diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h index bd0d7549d20f..f00ca2983ff5 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_types.h @@ -279,6 +279,8 @@ struct dml2_per_stream_programming { enum dml2_pstate_method uclk_pstate_method; + struct dml2_mcif_per_pipe_register_set *mcif_regs[DML2_MAX_WRITEBACK]; + struct { bool enabled; struct dml2_stream_parameters descriptor; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c index c983869e0fa3..727b01ca18bf 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c @@ -368,9 +368,10 @@ static void expand_implict_subvp(const struct display_configuation_with_meta *di static void pack_mode_programming_params_with_implicit_subvp(struct dml2_core_instance *core, const struct display_configuation_with_meta *display_cfg, const struct dml2_display_cfg *svp_expanded_display_cfg, struct dml2_display_cfg_programming *programming, struct dml2_core_scratch *scratch) { - unsigned int stream_index, plane_index, pipe_offset, stream_already_populated_mask, main_plane_index, mcache_index; + unsigned int stream_index, plane_index, pipe_offset, stream_already_populated_mask, main_plane_index, mcache_index, dwb_index; unsigned int total_main_mcaches_required = 0; int total_pipe_regs_copied = 0; + int total_dwb_regs_copied = 0; int dml_internal_pipe_index = 0; const struct dml2_plane_parameters *main_plane; const struct dml2_plane_parameters *phantom_plane; @@ -386,6 +387,8 @@ static void pack_mode_programming_params_with_implicit_subvp(struct dml2_core_in // display config dml2_core_calcs_get_watermarks(&display_cfg->display_config, &core->clean_me_up.mode_lib, &programming->global_regs.wm_regs[0]); + dml2_core_calcs_get_mcif_arb_params(&core->clean_me_up.mode_lib, &programming->mcif_global_regs); + // Check if FAMS2 is required if (display_cfg->stage3.performed && display_cfg->stage3.success) { programming->fams2_required = display_cfg->stage3.fams2_required; @@ -470,6 +473,19 @@ static void pack_mode_programming_params_with_implicit_subvp(struct dml2_core_in programming->stream_programming[main_plane->stream_index].uclk_pstate_method, plane_index); + /* populate DWB */ + for (dwb_index = 0; dwb_index < svp_expanded_display_cfg->stream_descriptors[main_plane->stream_index].writeback.active_writebacks_per_stream; dwb_index++) { + programming->stream_programming[main_plane->stream_index].mcif_regs[dwb_index] = &programming->mcif_regs[total_dwb_regs_copied]; + memset(programming->stream_programming[main_plane->stream_index].mcif_regs[dwb_index], 0, sizeof(struct dml2_mcif_per_pipe_register_set)); + total_dwb_regs_copied++; + + dml2_core_calcs_get_per_dwb_params(svp_expanded_display_cfg, + &core->clean_me_up.mode_lib, + programming->stream_programming[main_plane->stream_index].mcif_regs[dwb_index], + main_plane->stream_index, + dwb_index); + } + stream_already_populated_mask |= (0x1 << main_plane->stream_index); } dml_internal_pipe_index++; @@ -661,10 +677,12 @@ bool core_dcn4_mode_programming(struct dml2_core_mode_programming_in_out *in_out unsigned int pipe_offset; int dml_internal_pipe_index; int total_pipe_regs_copied = 0; + int total_dwb_regs_copied = 0; int stream_already_populated_mask = 0; int main_stream_index; unsigned int plane_index; + unsigned int dwb_index; expand_implict_subvp(in_out->display_cfg, &l->svp_expanded_display_cfg, &core->scratch); @@ -688,6 +706,8 @@ bool core_dcn4_mode_programming(struct dml2_core_mode_programming_in_out *in_out dml2_core_calcs_get_arb_params(&l->svp_expanded_display_cfg, &core->clean_me_up.mode_lib, &in_out->programming->global_regs.arb_regs); dml2_core_calcs_get_watermarks(&l->svp_expanded_display_cfg, &core->clean_me_up.mode_lib, &in_out->programming->global_regs.wm_regs[0]); + dml2_core_calcs_get_mcif_arb_params(&core->clean_me_up.mode_lib, &in_out->programming->mcif_global_regs); + dml_internal_pipe_index = 0; for (plane_index = 0; plane_index < in_out->programming->display_config.num_planes; plane_index++) { @@ -733,6 +753,18 @@ bool core_dcn4_mode_programming(struct dml2_core_mode_programming_in_out *in_out in_out->programming->stream_programming[main_stream_index].num_odms_required = in_out->cfg_support_info->stream_support_info[main_stream_index].odms_used; dml2_core_calcs_get_stream_programming(&core->clean_me_up.mode_lib, &in_out->programming->stream_programming[main_stream_index], dml_internal_pipe_index); + for (dwb_index = 0; dwb_index < l->svp_expanded_display_cfg.stream_descriptors[main_stream_index].writeback.active_writebacks_per_stream; dwb_index++) { + in_out->programming->stream_programming[main_stream_index].mcif_regs[dwb_index] = &in_out->programming->mcif_regs[total_dwb_regs_copied]; + memset(in_out->programming->stream_programming[main_stream_index].mcif_regs[dwb_index], 0, sizeof(struct dml2_mcif_per_pipe_register_set)); + total_dwb_regs_copied++; + + dml2_core_calcs_get_per_dwb_params(&l->svp_expanded_display_cfg, + &core->clean_me_up.mode_lib, + in_out->programming->stream_programming[main_stream_index].mcif_regs[dwb_index], + main_stream_index, + dwb_index); + } + stream_already_populated_mask |= (0x1 << main_stream_index); } dml_internal_pipe_index++; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c index b667fc9ad75f..fa78016b32fc 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c @@ -6741,6 +6741,25 @@ static void CalculateFlipSchedule( #endif } +static double calculate_writeback_latency_hiding_us( + const struct dml2_display_cfg *display_cfg, + unsigned int writeback_buffer_size_bytes, + unsigned int stream_index, + unsigned int dwb_index) +{ + double line_time_us = (double)display_cfg->stream_descriptors[stream_index].timing.h_total / + (double)display_cfg->stream_descriptors[stream_index].timing.pixel_clock_khz / 1000.0; + + double writeback_latency_hiding_us = (double)writeback_buffer_size_bytes / + ((double)display_cfg->stream_descriptors[stream_index].writeback.writeback_stream[dwb_index].output_height * + (double)display_cfg->stream_descriptors[stream_index].writeback.writeback_stream[dwb_index].output_width / + ((double)display_cfg->stream_descriptors[stream_index].writeback.writeback_stream[dwb_index].input_height * + line_time_us) * 4.0); + + return display_cfg->stream_descriptors[stream_index].writeback.writeback_stream[dwb_index].pixel_format == dml2_444_64 ? + writeback_latency_hiding_us / 2 : writeback_latency_hiding_us; +} + static void CalculateWatermarksMALLUseAndDRAMSpeedChangeSupport( struct dml2_core_internal_scratch *scratch, struct dml2_core_calcs_CalculateWatermarksMALLUseAndDRAMSpeedChangeSupport_params *p) @@ -6905,13 +6924,10 @@ static void CalculateWatermarksMALLUseAndDRAMSpeedChangeSupport( p->VActiveLatencyHidingUs[k] = s->ActiveClockChangeLatencyHiding; if (p->display_cfg->stream_descriptors[p->display_cfg->plane_descriptors[k].stream_index].writeback.active_writebacks_per_stream > 0) { - s->WritebackLatencyHiding = (double)p->WritebackInterfaceBufferSize * 1024.0 - / ((double)p->display_cfg->stream_descriptors[p->display_cfg->plane_descriptors[k].stream_index].writeback.writeback_stream[0].output_height - * (double)p->display_cfg->stream_descriptors[p->display_cfg->plane_descriptors[k].stream_index].writeback.writeback_stream[0].output_width - / ((double)p->display_cfg->stream_descriptors[p->display_cfg->plane_descriptors[k].stream_index].writeback.writeback_stream[0].input_height * (double)h_total / pixel_clock_mhz) * 4.0); - if (p->display_cfg->stream_descriptors[p->display_cfg->plane_descriptors[k].stream_index].writeback.writeback_stream[0].pixel_format == dml2_444_64) { - s->WritebackLatencyHiding = s->WritebackLatencyHiding / 2; - } + s->WritebackLatencyHiding = calculate_writeback_latency_hiding_us(p->display_cfg, + p->WritebackInterfaceBufferSize * 1024, + p->display_cfg->plane_descriptors[k].stream_index, + 0); s->WritebackDRAMClockChangeLatencyMargin = s->WritebackLatencyHiding - p->Watermark->WritebackDRAMClockChangeWatermark; s->WritebackFCLKChangeLatencyMargin = s->WritebackLatencyHiding - p->Watermark->WritebackFCLKChangeWatermark; @@ -8693,6 +8709,7 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out s->TotalNumberOfActiveWriteback = 0; memset(s->stream_visited, 0, DML2_MAX_PLANES * sizeof(bool)); + mode_lib->ms.support.EnoughWritebackUnits = true; for (k = 0; k < mode_lib->ms.num_active_planes; ++k) { if (!dml_is_phantom_pipe(&display_cfg->plane_descriptors[k])) { if (!s->stream_visited[display_cfg->plane_descriptors[k].stream_index]) { @@ -8701,6 +8718,10 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out if (display_cfg->stream_descriptors[display_cfg->plane_descriptors[k].stream_index].writeback.active_writebacks_per_stream > 0) s->TotalNumberOfActiveWriteback = s->TotalNumberOfActiveWriteback + 1; + /* >1 writeback per stream is currently not supported */ + if (display_cfg->stream_descriptors[display_cfg->plane_descriptors[k].stream_index].writeback.active_writebacks_per_stream > 1) + mode_lib->ms.support.EnoughWritebackUnits = false; + s->TotalNumberOfActiveOTG = s->TotalNumberOfActiveOTG + 1; if (display_cfg->stream_descriptors[display_cfg->plane_descriptors[k].stream_index].output.output_encoder == dml2_hdmifrl) s->TotalNumberOfActiveHDMIFRL = s->TotalNumberOfActiveHDMIFRL + 1; @@ -8716,10 +8737,10 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out } /* Writeback Mode Support Check */ - mode_lib->ms.support.EnoughWritebackUnits = 1; if (s->TotalNumberOfActiveWriteback > (unsigned int)mode_lib->ip.max_num_wb) { mode_lib->ms.support.EnoughWritebackUnits = false; } + mode_lib->ms.support.NumberOfOTGSupport = (s->TotalNumberOfActiveOTG <= (unsigned int)mode_lib->ip.max_num_otg); mode_lib->ms.support.NumberOfHDMIFRLSupport = (s->TotalNumberOfActiveHDMIFRL <= (unsigned int)mode_lib->ip.max_num_hdmi_frl_outputs); mode_lib->ms.support.NumberOfDP2p0Support = (s->TotalNumberOfActiveDP2p0 <= (unsigned int)mode_lib->ip.max_num_dp2p0_streams && s->TotalNumberOfActiveDP2p0Outputs <= (unsigned int)mode_lib->ip.max_num_dp2p0_outputs); @@ -12831,6 +12852,14 @@ void dml2_core_calcs_get_arb_params(const struct dml2_display_cfg *display_cfg, rq_dlg_get_arb_params(display_cfg, mode_lib, out); } +void dml2_core_calcs_get_mcif_arb_params(const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_mcif_global_register_set *out) +{ + out->wm_regs[0].fclk_pstate = (unsigned int)(mode_lib->mp.Watermark.WritebackFCLKChangeWatermark * 1000.0); + out->wm_regs[0].uclk_pstate = (unsigned int)(mode_lib->mp.Watermark.WritebackDRAMClockChangeWatermark * 1000.0); + out->wm_regs[0].urgent = (unsigned int)(mode_lib->mp.Watermark.WritebackUrgentWatermark * 1000.0); + out->wm_regs[0].temp_read_or_ppt = (unsigned int)(mode_lib->mp.Watermark.writeback_temp_read_or_ppt_watermark_us * 1000.0); +} + void dml2_core_calcs_get_pipe_regs(const struct dml2_display_cfg *display_cfg, struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_dchub_per_pipe_register_set *out, int pipe_index) @@ -12854,6 +12883,29 @@ void dml2_core_calcs_get_stream_programming(const struct dml2_core_internal_disp dml2_core_calcs_get_global_sync_programming(mode_lib, &out->global_sync, pipe_index); } +void dml2_core_calcs_get_per_dwb_params(const struct dml2_display_cfg *display_cfg, + const struct dml2_core_internal_display_mode_lib *mode_lib, + struct dml2_mcif_per_pipe_register_set *out, + int stream_index, + int dwb_index) +{ + double writeback_latency_hiding_us = calculate_writeback_latency_hiding_us(display_cfg, + mode_lib->ip.writeback_interface_buffer_size_kbytes * 1024, + stream_index, + dwb_index); + + out->max_scaled_time_ns = (unsigned int)math_max2( + (writeback_latency_hiding_us - mode_lib->mp.Watermark.WritebackUrgentWatermark) * 1000.0, + 0.0); + + /* 1024ps units in U6.6 format */ + out->time_per_pixel = (unsigned int)((1000000.0 * math_pow(2, 6)) / + (double)display_cfg->stream_descriptors[stream_index].timing.pixel_clock_khz); + + out->slice_lines = 31; + out->arbitration_slice = 2; +} + void dml2_core_calcs_get_global_fams2_programming(const struct dml2_core_internal_display_mode_lib *mode_lib, const struct display_configuation_with_meta *display_cfg, struct dmub_cmd_fams2_global_config *fams2_global_config) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.h index 27ef0e096b25..3249f6bcf7bc 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.h @@ -19,6 +19,7 @@ struct display_configuation_with_meta; unsigned int dml2_core_calcs_mode_support_ex(struct dml2_core_calcs_mode_support_ex *in_out_params); bool dml2_core_calcs_mode_programming_ex(struct dml2_core_calcs_mode_programming_ex *in_out_params); void dml2_core_calcs_get_watermarks(const struct dml2_display_cfg *display_cfg, const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_dchub_watermark_regs *out); +void dml2_core_calcs_get_mcif_arb_params(const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_mcif_global_register_set *out); void dml2_core_calcs_get_arb_params(const struct dml2_display_cfg *display_cfg, const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_display_arb_regs *out); void dml2_core_calcs_get_pipe_regs(const struct dml2_display_cfg *dml2_display_cfg, struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_dchub_per_pipe_register_set *out, int pipe_index); void dml2_core_calcs_get_stream_programming(const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_per_stream_programming *out, int pipe_index); @@ -30,6 +31,7 @@ void dml2_core_calcs_get_stream_support_info(const struct dml2_display_cfg *disp void dml2_core_calcs_get_mall_allocation(struct dml2_core_internal_display_mode_lib *mode_lib, unsigned int *out, int pipe_index); void dml2_core_calcs_get_stream_fams2_programming(const struct dml2_core_internal_display_mode_lib *mode_lib, const struct display_configuation_with_meta *display_cfg, union dmub_cmd_fams2_config *fams2_base_programming, union dmub_cmd_fams2_config *fams2_sub_programming, enum dml2_pstate_method pstate_method, int plane_index); void dml2_core_calcs_get_global_fams2_programming(const struct dml2_core_internal_display_mode_lib *mode_lib, const struct display_configuation_with_meta *display_cfg, struct dmub_cmd_fams2_global_config *fams2_global_config); +void dml2_core_calcs_get_per_dwb_params(const struct dml2_display_cfg *display_cfg, const struct dml2_core_internal_display_mode_lib *mode_lib, struct dml2_mcif_per_pipe_register_set *out, int stream_index, int dwb_index); void dml2_core_calcs_get_dpte_row_height(unsigned int *dpte_row_height, struct dml2_core_internal_display_mode_lib *mode_lib, bool is_plane1, enum dml2_source_format_class SourcePixelFormat, enum dml2_swizzle_mode SurfaceTiling, enum dml2_rotation_angle ScanDirection, unsigned int pitch, unsigned int GPUVMMinPageSizeKBytes); void dml2_core_calcs_cursor_dlg_reg(struct dml2_cursor_dlg_regs *cursor_dlg_regs, const struct dml2_get_cursor_dlg_reg *p); diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_dpmm/dml2_dpmm_dcn4.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_dpmm/dml2_dpmm_dcn4.c index 5ffe211a6643..c60a1faf2d5d 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_dpmm/dml2_dpmm_dcn4.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_dpmm/dml2_dpmm_dcn4.c @@ -779,6 +779,25 @@ bool dpmm_dcn4_map_mode_to_soc_dpm(struct dml2_dpmm_map_mode_to_soc_dpm_params_i return result; } +static void dpmm_dcn4_map_mcif_watermarks(struct dml2_dpmm_map_watermarks_params_in_out *in_out) +{ + const struct dml2_core_internal_display_mode_lib *mode_lib = &in_out->core->clean_me_up.mode_lib; + struct dml2_mcif_global_register_set *mcif_regs = &in_out->programming->mcif_global_regs; + + /* MCIF */ + mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A].urgent = (int unsigned)(mode_lib->mp.Watermark.WritebackUrgentWatermark * 1000.0); + mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A].uclk_pstate = (int unsigned)(mode_lib->mp.Watermark.WritebackDRAMClockChangeWatermark * 1000.0); + mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A].fclk_pstate = (int unsigned)(mode_lib->mp.Watermark.WritebackFCLKChangeWatermark * 1000.0); + mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A].temp_read_or_ppt = (int unsigned)(mode_lib->mp.Watermark.writeback_temp_read_or_ppt_watermark_us * 1000.0); + + /* replicate sets A through D */ + memcpy(&mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_B], &mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A], sizeof(mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A])); + memcpy(&mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_C], &mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A], sizeof(mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A])); + memcpy(&mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_D], &mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A], sizeof(mcif_regs->wm_regs[DML2_DCHUB_WATERMARK_SET_A])); + + mcif_regs->num_watermark_sets = 4; +} + bool dpmm_dcn4_map_watermarks(struct dml2_dpmm_map_watermarks_params_in_out *in_out) { const struct dml2_display_cfg *display_cfg = &in_out->display_cfg->display_config; @@ -821,6 +840,8 @@ bool dpmm_dcn4_map_watermarks(struct dml2_dpmm_map_watermarks_params_in_out *in_ dchubbub_regs->num_watermark_sets = 2; + dpmm_dcn4_map_mcif_watermarks(in_out); + return true; } bool dpmm_dcn42_map_watermarks(struct dml2_dpmm_map_watermarks_params_in_out *in_out) -- cgit v1.2.3 From 843049bac7c0a08c5263bed5a03f8fa23da19513 Mon Sep 17 00:00:00 2001 From: Dominik Kaszewski Date: Fri, 19 Jun 2026 15:21:35 +0200 Subject: drm/amd/display: Split DPMS ON into parts [Why] Ongoing optimization efforts require splitting DPMS ON around enable_link, in order to enable running multiple sequences in parallel. [How] * Split link_set_dpms_on across enable_link * Add return value indicating whether the programming sequence succeeded and/or run to the end. Reviewed-by: Alvin Lee Signed-off-by: Dominik Kaszewski Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/inc/core_status.h | 10 ++ drivers/gpu/drm/amd/display/dc/inc/link_service.h | 4 +- drivers/gpu/drm/amd/display/dc/link/link_dpms.c | 184 +++++++++++++++++----- drivers/gpu/drm/amd/display/dc/link/link_dpms.h | 4 +- 4 files changed, 156 insertions(+), 46 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/inc/core_status.h b/drivers/gpu/drm/amd/display/dc/inc/core_status.h index 388f801f4582..1a17e727ed04 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/core_status.h +++ b/drivers/gpu/drm/amd/display/dc/inc/core_status.h @@ -62,6 +62,16 @@ enum dc_status { DC_FAIL_DP_LINK_BANDWIDTH = 28, DC_FAIL_HW_CURSOR_SUPPORT = 29, DC_FAIL_DP_TUNNEL_BW_VALIDATE = 30, + + /// Link protocol handshake and DPMS hardware programming successful. + DC_DPMS_SUCCESS = DC_OK, + /// Handshake skipped by optimized path, programming successfully completed. + DC_DPMS_SKIPPED_HANDSHAKE = 31, + /// Handshake failed, programming successful, DCN in consistent state. + DC_DPMS_FAILED_HANDSHAKE = 32, + /// Handshake failed, programming aborted, DCN may be in inconsistent state. + DC_DPMS_FAILED_INCOMPLETE = 33, + DC_ERROR_UNEXPECTED = -1 }; diff --git a/drivers/gpu/drm/amd/display/dc/inc/link_service.h b/drivers/gpu/drm/amd/display/dc/inc/link_service.h index addeb3e3b25a..026d28046eea 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/link_service.h +++ b/drivers/gpu/drm/amd/display/dc/inc/link_service.h @@ -155,8 +155,8 @@ struct link_service { /*************************** DPMS *************************************/ - void (*set_dpms_on)(struct dc_state *state, struct pipe_ctx *pipe_ctx); - void (*set_dpms_off)(struct pipe_ctx *pipe_ctx); + enum dc_status (*set_dpms_on)(struct dc_state *state, struct pipe_ctx *pipe_ctx); + enum dc_status (*set_dpms_off)(struct pipe_ctx *pipe_ctx); void (*resume)(struct dc_link *link); void (*blank_all_dp_displays)(struct dc *dc); void (*blank_all_edp_displays)(struct dc *dc); diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c index 335ae952ef60..949c669eb259 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c @@ -528,19 +528,27 @@ static bool write_i2c_redriver_setting( return success; } +static struct link_encoder *get_link_encoder(struct pipe_ctx *pipe_ctx) +{ + struct link_encoder *link_enc + = pipe_ctx->stream->ctx->dc->config.unify_link_enc_assignment + ? pipe_ctx->link_res.dio_link_enc + : link_enc_cfg_get_link_enc(pipe_ctx->stream->link); + + ASSERT(link_enc); + return link_enc; +} + static void update_psp_stream_config(struct pipe_ctx *pipe_ctx, bool dpms_off) { struct cp_psp *cp_psp = &pipe_ctx->stream->ctx->cp_psp; - struct link_encoder *link_enc = pipe_ctx->link_res.dio_link_enc; + struct link_encoder *link_enc = get_link_encoder(pipe_ctx); struct cp_psp_stream_config config = {0}; enum dp_panel_mode panel_mode = dp_get_panel_mode(pipe_ctx->stream->link); if (cp_psp == NULL || cp_psp->funcs.update_stream_config == NULL) return; - if (!pipe_ctx->stream->ctx->dc->config.unify_link_enc_assignment) - link_enc = link_enc_cfg_get_link_enc(pipe_ctx->stream->link); - ASSERT(link_enc); if (link_enc == NULL) return; @@ -2375,7 +2383,7 @@ static struct vpg *get_vpg(struct pipe_ctx *pipe_ctx) return pipe_ctx->stream_res.stream_enc->vpg; } -void link_set_dpms_off(struct pipe_ctx *pipe_ctx) +enum dc_status link_set_dpms_off(struct pipe_ctx *pipe_ctx) { DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); struct dc_stream_state *stream = pipe_ctx->stream; @@ -2387,7 +2395,8 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) ASSERT(is_master_pipe_for_link(link, pipe_ctx)); if (dc_is_virtual_signal(stream->signal)) - return; + /* No real hardware to program for virtual signals. */ + return DC_DPMS_SKIPPED_HANDSHAKE; if (stream->sink) { if (stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && @@ -2481,18 +2490,23 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) /* since current psp not loaded, we need to reset it to default */ link->panel_mode = panel_mode; } + + return DC_DPMS_SUCCESS; } -void link_set_dpms_on( +static enum dc_status link_set_dpms_on_pre_enable_link( struct dc_state *state, - struct pipe_ctx *pipe_ctx) + struct pipe_ctx *pipe_ctx +) { + // Used conditionally in ifdef'ed diagnostic builds + (void) state; + DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); struct dc_stream_state *stream = pipe_ctx->stream; struct dc *dc = stream->ctx->dc; struct dc_link *link = stream->link; - enum dc_status status; - struct link_encoder *link_enc = pipe_ctx->link_res.dio_link_enc; + enum otg_out_mux_dest otg_out_dest = OUT_MUX_DIO; struct vpg *vpg = get_vpg(pipe_ctx); const struct link_hwss *link_hwss = get_link_hwss(link, &pipe_ctx->link_res); @@ -2502,7 +2516,8 @@ void link_set_dpms_on( ASSERT(is_master_pipe_for_link(link, pipe_ctx)); if (dc_is_virtual_signal(stream->signal)) - return; + /* No real hardware to program for virtual signals. */ + return DC_DPMS_SKIPPED_HANDSHAKE; if (stream->sink) { if (stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && @@ -2516,13 +2531,12 @@ void link_set_dpms_on( } link_wait_for_unlocked(link); - if (!dc->config.unify_link_enc_assignment) - link_enc = link_enc_cfg_get_link_enc(link); - ASSERT(link_enc); if (!dc_is_virtual_signal(stream->signal) && !dc_is_hdmi_frl_signal(stream->signal) && !dp_is_128b_132b_signal(pipe_ctx)) { + struct link_encoder *link_enc = get_link_encoder(pipe_ctx); + if (link_enc) link_enc->funcs->setup( link_enc, @@ -2569,7 +2583,9 @@ void link_set_dpms_on( } update_psp_stream_config(pipe_ctx, false); - return; + + /* Seamless boot: hardware already enabled by BIOS; skip link training. */ + return DC_DPMS_SKIPPED_HANDSHAKE; } /* eDP lit up by bios already, no need to enable again. */ @@ -2587,11 +2603,16 @@ void link_set_dpms_on( msleep(post_oui_delay); } - return; + /* eDP already lit by BIOS; skip standard enable steps. */ + return DC_DPMS_SKIPPED_HANDSHAKE; } if (stream->dpms_off) - return; + /* + * Stream is configured as DPMS-off; skip link enable. + * Hardware will NOT be in a fully enabled state after this early exit. + */ + return DC_DPMS_FAILED_INCOMPLETE; /* For Dp tunneling link, a pending HPD means that we have a race condition between processing * current link and processing the pending HPD. If we enable the link now, we may end up with a @@ -2599,7 +2620,11 @@ void link_set_dpms_on( */ if (link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA && link->is_hpd_pending) { DC_LOG_DEBUG("%s, Link%d HPD is pending, not enable it.\n", __func__, link->link_index); - return; + /* + * Pending HPD on USB4 DPIA link: skip enable to avoid race condition. + * Hardware will NOT be in a fully enabled state after this early exit. + */ + return DC_DPMS_FAILED_INCOMPLETE; } /* Have to setup DSC before DIG FE and BE are connected (which happens before the @@ -2617,30 +2642,61 @@ void link_set_dpms_on( if (link->replay_settings.config.replay_supported && !dc_is_embedded_signal(link->connector_signal)) dp_setup_replay(link, stream); - // TODO: Split DPMS-on into 3 functions at this point - status = enable_link(state, pipe_ctx); + return DC_DPMS_SUCCESS; +} + +static enum dc_status link_set_dpms_on_enable_link( + struct dc_state *state, + struct pipe_ctx *pipe_ctx +) +{ + DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); + struct dc_stream_state *stream = pipe_ctx->stream; + struct dc_link *link = stream->link; + const enum dc_status status = enable_link(state, pipe_ctx); + + if (status == DC_OK) + return DC_DPMS_SUCCESS; - if (status != DC_OK) { - DC_LOG_WARNING("enabling link %u failed: %d\n", - link->link_index, - status); + DC_LOG_WARNING("enabling link %u failed: %d\n", link->link_index, status); - /* Abort stream enable *unless* the failure was due to - * DP link training - some DP monitors will recover and - * show the stream anyway. But MST displays can't proceed - * without link training. - */ - if ((status != DC_FAIL_DP_LINK_TRAINING && - status != DC_FAIL_HDMI_FRL_LINK_TRAINING) || - stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { - if (false == link->link_status.link_active) - disable_link(link, &pipe_ctx->link_res, - stream->signal); - BREAK_TO_DEBUGGER(); - return; - } + /* Abort stream enable *unless* the failure was due to + * DP link training - some DP monitors will recover and + * show the stream anyway. But MST displays can't proceed + * without link training. + */ + switch (status) { + case DC_FAIL_DP_LINK_TRAINING: + case DC_FAIL_HDMI_FRL_LINK_TRAINING: + if (stream->signal != SIGNAL_TYPE_DISPLAY_PORT_MST) + return DC_DPMS_SUCCESS; + break; + + default: + break; } - // TODO: Split DPMS-on into 3 functions at this point + + if (!link->link_status.link_active) + disable_link(link, &pipe_ctx->link_res, stream->signal); + + /* + * Link enable failed; do NOT set skip_remaining so that post_enable_link + * still runs and leaves hardware in a consistent state. + */ + return DC_DPMS_FAILED_HANDSHAKE; +} + +static enum dc_status link_set_dpms_on_post_enable_link( + struct dc_state *state, + struct pipe_ctx *pipe_ctx +) +{ + // Used conditionally in ifdef'ed diagnostic builds + (void) state; + + struct dc_stream_state *stream = pipe_ctx->stream; + struct dc_link *link = stream->link; + struct hw_sequencer_funcs *hwss = &stream->ctx->dc->hwss; if (stream->timing.flags.DSC && dc_is_hdmi_frl_signal(stream->signal)) //TODO: bring HDMI FRL in line with DP @@ -2659,13 +2715,15 @@ void link_set_dpms_on( if (!(dc_is_virtual_signal(stream->signal) || dc_is_hdmi_frl_signal(stream->signal) || dp_is_128b_132b_signal(pipe_ctx))) { + struct link_encoder *link_enc = get_link_encoder(pipe_ctx); + if (link_enc) link_enc->funcs->setup( link_enc, stream->signal); } - dc->hwss.enable_stream(pipe_ctx); + hwss->enable_stream(pipe_ctx); /* Set DPS PPS SDP (AKA "info frames") */ if (stream->timing.flags.DSC) { @@ -2697,7 +2755,7 @@ void link_set_dpms_on( link->is_display_mux_present) msleep(20); - dc->hwss.unblank_stream(pipe_ctx, + hwss->unblank_stream(pipe_ctx, &link->cur_link_settings); if (stream->sink_patches.delay_ignore_msa > 0) @@ -2707,8 +2765,50 @@ void link_set_dpms_on( enable_stream_features(pipe_ctx); update_psp_stream_config(pipe_ctx, false); - dc->hwss.enable_audio_stream(pipe_ctx); + hwss->enable_audio_stream(pipe_ctx); if (dc_is_hdmi_signal(stream->signal)) set_avmute(pipe_ctx, false); + + return DC_DPMS_SUCCESS; +} + +enum dc_status link_set_dpms_on( + struct dc_state *state, + struct pipe_ctx *pipe_ctx +) +{ + enum dc_status result = DC_DPMS_SUCCESS; + + typedef enum dc_status (*step)(struct dc_state *, struct pipe_ctx *); + const step steps[] = { + link_set_dpms_on_pre_enable_link, + link_set_dpms_on_enable_link, + link_set_dpms_on_post_enable_link, + }; + + for (size_t i = 0; i < ARRAY_SIZE(steps); i++) { + const enum dc_status step_result = steps[i](state, pipe_ctx); + + switch (step_result) { + case DC_DPMS_SUCCESS: + case DC_DPMS_FAILED_HANDSHAKE: + // Enum is ordered from "best" to "worst" results + result = max(result, step_result); + break; + + case DC_DPMS_SKIPPED_HANDSHAKE: + return step_result; + + case DC_DPMS_FAILED_INCOMPLETE: + ASSERT(false); + return step_result; + + default: + ASSERT(false); + return step_result; + } + } + + return result; } diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.h b/drivers/gpu/drm/amd/display/dc/link/link_dpms.h index e8662147dd8e..6559bc04d90e 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.h +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.h @@ -27,10 +27,10 @@ #define __DC_LINK_DPMS_H__ #include "link_service.h" -void link_set_dpms_on( +enum dc_status link_set_dpms_on( struct dc_state *state, struct pipe_ctx *pipe_ctx); -void link_set_dpms_off(struct pipe_ctx *pipe_ctx); +enum dc_status link_set_dpms_off(struct pipe_ctx *pipe_ctx); void link_resume(struct dc_link *link); void link_blank_all_dp_displays(struct dc *dc); void link_blank_all_edp_displays(struct dc *dc); -- cgit v1.2.3 From a10bb08af58198c341f1b3d60a65c2582535cdc3 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 30 Jun 2026 13:09:15 -0600 Subject: drm/amd/display: Test color mod init and 3D LUT size [WHAT] Add KUnit coverage for amdgpu_dm_init_color_mod() and amdgpu_dm_verify_lut3d_size(). init_color_mod is a smoke test that the x-points distribution is set up without crashing. The verify_lut3d_size tests build adev/DC fixtures with and without the 3D LUT capability and confirm that correct shaper and 3D LUT blob sizes succeed while mismatched sizes return -EINVAL. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 2 + .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 123 +++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index ab6e0cfb1572..1a0a7a351103 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -172,6 +172,7 @@ void amdgpu_dm_init_color_mod(void) { setup_x_points_distribution(); } +EXPORT_IF_KUNIT(amdgpu_dm_init_color_mod); STATIC_IFN_KUNIT INLINE_IFN_KUNIT struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x) @@ -1177,6 +1178,7 @@ int amdgpu_dm_verify_lut3d_size(struct amdgpu_device *adev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_verify_lut3d_size); /** * amdgpu_dm_verify_lut_sizes - verifies if DRM luts match the hw supported sizes diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index d64c7da20f2c..e46a8454425d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -7,14 +7,17 @@ #include #include +#include #include #include #include #include "dc.h" +#include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_color.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* ---- Tests for amdgpu_dm_fixpt_from_s3132 ---- */ @@ -1527,6 +1530,119 @@ static void dm_test_set_colorop_in_tf_1d_curve_bypass(struct kunit *test) (int)TRANSFER_FUNCTION_LINEAR); } +/* ---- Tests for amdgpu_dm_init_color_mod ---- */ + +/** + * dm_test_init_color_mod - Smoke test: must initialize x-points without crashing + * @test: KUnit test context + */ +static void dm_test_init_color_mod(struct kunit *test) +{ + amdgpu_dm_init_color_mod(); + KUNIT_SUCCEED(test); +} + +/* ---- Tests for amdgpu_dm_verify_lut3d_size ---- */ + +/** + * dm_test_verify_lut3d_alloc_plane - Allocate a dm_plane_state for lut3d tests + * @test: KUnit test context + * + * Returns: a drm_plane_state pointer embedded in a zeroed dm_plane_state. + */ +static struct drm_plane_state * +dm_test_verify_lut3d_alloc_plane(struct kunit *test) +{ + struct dm_plane_state *dm_plane_state; + + dm_plane_state = kunit_kzalloc(test, sizeof(*dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + + return &dm_plane_state->base; +} + +/** + * dm_test_verify_lut3d_alloc_adev - Allocate adev with a DC and given 3D LUT cap + * @test: KUnit test context + * @has_3dlut: value to program into caps.color.dpp.hw_3d_lut + * + * Returns: an amdgpu_device with adev->dm.dc allocated. + */ +static struct amdgpu_device * +dm_test_verify_lut3d_alloc_adev(struct kunit *test, bool has_3dlut) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + + adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc); + adev->dm.dc->caps.color.dpp.hw_3d_lut = has_3dlut; + + return adev; +} + +/** + * dm_test_verify_lut3d_no_luts - No shaper/3D LUT blobs: must succeed + * @test: KUnit test context + */ +static void dm_test_verify_lut3d_no_luts(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_verify_lut3d_alloc_adev(test, false); + struct drm_plane_state *plane_state = dm_test_verify_lut3d_alloc_plane(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut3d_size(adev, plane_state), 0); +} + +/** + * dm_test_verify_lut3d_bad_shaper - Shaper LUT with wrong size: must return -EINVAL + * @test: KUnit test context + */ +static void dm_test_verify_lut3d_bad_shaper(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_verify_lut3d_alloc_adev(test, true); + struct drm_plane_state *plane_state = dm_test_verify_lut3d_alloc_plane(test); + struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); + + /* has_3dlut => expected shaper size is MAX_COLOR_LUT_ENTRIES */ + dm_plane_state->shaper_lut = dm_test_make_lut_blob(test, 128); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut3d_size(adev, plane_state), -EINVAL); +} + +/** + * dm_test_verify_lut3d_bad_lut3d - 3D LUT with wrong size: must return -EINVAL + * @test: KUnit test context + */ +static void dm_test_verify_lut3d_bad_lut3d(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_verify_lut3d_alloc_adev(test, true); + struct drm_plane_state *plane_state = dm_test_verify_lut3d_alloc_plane(test); + struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); + + /* Valid shaper, but wrong 3D LUT size */ + dm_plane_state->shaper_lut = dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + dm_plane_state->lut3d = dm_test_make_lut_blob(test, 128); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut3d_size(adev, plane_state), -EINVAL); +} + +/** + * dm_test_verify_lut3d_valid - Correct shaper and 3D LUT sizes: must succeed + * @test: KUnit test context + */ +static void dm_test_verify_lut3d_valid(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_verify_lut3d_alloc_adev(test, true); + struct drm_plane_state *plane_state = dm_test_verify_lut3d_alloc_plane(test); + struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); + const uint32_t lut3d_entries = + MAX_COLOR_3DLUT_SIZE * MAX_COLOR_3DLUT_SIZE * MAX_COLOR_3DLUT_SIZE; + + dm_plane_state->shaper_lut = dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + dm_plane_state->lut3d = dm_test_make_lut_blob(test, lut3d_entries); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut3d_size(adev, plane_state), 0); +} + static struct kunit_case dm_color_test_cases[] = { /* amdgpu_dm_fixpt_from_s3132 */ KUNIT_CASE(dm_test_fixpt_from_s3132_zero), @@ -1624,6 +1740,13 @@ static struct kunit_case dm_color_test_cases[] = { KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_invalid_type), KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_unsupported_curve), KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_bypass), + /* amdgpu_dm_init_color_mod */ + KUNIT_CASE(dm_test_init_color_mod), + /* amdgpu_dm_verify_lut3d_size */ + KUNIT_CASE(dm_test_verify_lut3d_no_luts), + KUNIT_CASE(dm_test_verify_lut3d_bad_shaper), + KUNIT_CASE(dm_test_verify_lut3d_bad_lut3d), + KUNIT_CASE(dm_test_verify_lut3d_valid), {} }; -- cgit v1.2.3 From 789f7486a384ca58b9d7a76abba5efa0e80e8dab Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 30 Jun 2026 13:09:42 -0600 Subject: drm/amd/display: Test plane colorop helper walkers [WHAT] Add KUnit coverage for the per-colorop plane helpers __set_dm_plane_colorop_multiplier(), __set_dm_plane_colorop_3x4_matrix() and __set_dm_plane_colorop_degamma(). A single-colorop atomic-state fixture drives each helper: the multiplier programs hdr_mult, the 3x4 matrix enables gamut remap (and rejects a wrong blob length), and the degamma 1D curve maps to a predefined transfer function (and rejects an unsupported curve). Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 9 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.h | 11 ++ .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 207 +++++++++++++++++++++ 3 files changed, 224 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 1a0a7a351103..be0925c4e3e5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -1547,7 +1547,7 @@ __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state, } EXPORT_IF_KUNIT(__set_colorop_in_tf_1d_curve); -static int +STATIC_IFN_KUNIT int __set_dm_plane_colorop_degamma(struct drm_plane_state *plane_state, struct dc_plane_state *dc_plane_state, struct drm_colorop *colorop) @@ -1573,8 +1573,9 @@ __set_dm_plane_colorop_degamma(struct drm_plane_state *plane_state, return __set_colorop_in_tf_1d_curve(dc_plane_state, colorop_state); } +EXPORT_IF_KUNIT(__set_dm_plane_colorop_degamma); -static int +STATIC_IFN_KUNIT int __set_dm_plane_colorop_3x4_matrix(struct drm_plane_state *plane_state, struct dc_plane_state *dc_plane_state, struct drm_colorop *colorop) @@ -1614,8 +1615,9 @@ __set_dm_plane_colorop_3x4_matrix(struct drm_plane_state *plane_state, return 0; } +EXPORT_IF_KUNIT(__set_dm_plane_colorop_3x4_matrix); -static int +STATIC_IFN_KUNIT int __set_dm_plane_colorop_multiplier(struct drm_plane_state *plane_state, struct dc_plane_state *dc_plane_state, struct drm_colorop *colorop) @@ -1643,6 +1645,7 @@ __set_dm_plane_colorop_multiplier(struct drm_plane_state *plane_state, return 0; } +EXPORT_IF_KUNIT(__set_dm_plane_colorop_multiplier); static int __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h index 8dbbcb3ab156..1a8b06bdaf44 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h @@ -113,6 +113,17 @@ int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut, struct dc_plane_cm *cm); int __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state, struct drm_colorop_state *colorop_state); +struct drm_plane_state; +struct drm_colorop; +int __set_dm_plane_colorop_degamma(struct drm_plane_state *plane_state, + struct dc_plane_state *dc_plane_state, + struct drm_colorop *colorop); +int __set_dm_plane_colorop_3x4_matrix(struct drm_plane_state *plane_state, + struct dc_plane_state *dc_plane_state, + struct drm_colorop *colorop); +int __set_dm_plane_colorop_multiplier(struct drm_plane_state *plane_state, + struct dc_plane_state *dc_plane_state, + struct drm_colorop *colorop); #endif #endif /* __AMDGPU_DM_COLOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index e46a8454425d..ec0dd0025ae6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -1643,6 +1643,204 @@ static void dm_test_verify_lut3d_valid(struct kunit *test) KUNIT_EXPECT_EQ(test, amdgpu_dm_verify_lut3d_size(adev, plane_state), 0); } +/* ---- Tests for plane colorop helpers ---- */ + +/** + * struct dm_test_colorop_fixture - shared state for plane colorop walk tests + * @adev: backing amdgpu device (provides a real DRM device) + * @state: fabricated atomic state with a single colorop slot + * @colorop: the colorop under test + * @colorop_state: the new state attached to @colorop + * @plane_state: plane state whose ->state points at @state + * @dc_plane_state: DC plane state written by the helpers + */ +struct dm_test_colorop_fixture { + struct amdgpu_device *adev; + struct drm_atomic_commit *state; + struct drm_colorop *colorop; + struct drm_colorop_state *colorop_state; + struct drm_plane_state *plane_state; + struct dc_plane_state *dc_plane_state; +}; + +/** + * dm_test_colorop_setup - build a single-colorop atomic state fixture + * @test: KUnit test context + * @type: colorop type to assign + * + * Fabricates a minimal drm_atomic_commit with one colorop slot so that + * for_each_new_colorop_in_state() finds exactly the colorop under test. + * + * Returns: a populated fixture (by value). + */ +static struct dm_test_colorop_fixture +dm_test_colorop_setup(struct kunit *test, enum drm_colorop_type type) +{ + struct dm_test_colorop_fixture f = {0}; + struct __drm_colorops_state *colorops; + struct dm_plane_state *dm_plane_state; + + f.adev = dm_kunit_alloc_adev(test); + f.adev->ddev.mode_config.num_colorop = 1; + + f.colorop = kunit_kzalloc(test, sizeof(*f.colorop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.colorop); + f.colorop->dev = &f.adev->ddev; + f.colorop->type = type; + + f.colorop_state = kunit_kzalloc(test, sizeof(*f.colorop_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.colorop_state); + f.colorop_state->colorop = f.colorop; + + colorops = kunit_kcalloc(test, 1, sizeof(*colorops), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorops); + colorops[0].ptr = f.colorop; + colorops[0].new_state = f.colorop_state; + + f.state = kunit_kzalloc(test, sizeof(*f.state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.state); + f.state->dev = &f.adev->ddev; + f.state->colorops = colorops; + + dm_plane_state = kunit_kzalloc(test, sizeof(*dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); + f.plane_state = &dm_plane_state->base; + f.plane_state->state = f.state; + + f.dc_plane_state = kunit_kzalloc(test, sizeof(*f.dc_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.dc_plane_state); + + return f; +} + +/** + * dm_test_colorop_multiplier_applied - Multiplier colorop programs hdr_mult + * @test: KUnit test context + */ +static void dm_test_colorop_multiplier_applied(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_MULTIPLIER); + + /* 1.0 in S31.32 sign-magnitude */ + f.colorop_state->multiplier = 1ULL << 32; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_multiplier(f.plane_state, f.dc_plane_state, f.colorop), + 0); + KUNIT_EXPECT_EQ(test, f.dc_plane_state->hdr_mult.value, (long long)(1ULL << 32)); +} + +/** + * dm_test_colorop_multiplier_no_match - Non-multiplier colorop leaves hdr_mult untouched + * @test: KUnit test context + */ +static void dm_test_colorop_multiplier_no_match(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_1D_CURVE); + + f.colorop_state->multiplier = 1ULL << 32; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_multiplier(f.plane_state, f.dc_plane_state, f.colorop), + 0); + KUNIT_EXPECT_EQ(test, f.dc_plane_state->hdr_mult.value, 0LL); +} + +/** + * dm_test_colorop_3x4_matrix_applied - CTM 3x4 colorop enables gamut remap + * @test: KUnit test context + */ +static void dm_test_colorop_3x4_matrix_applied(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_CTM_3X4); + struct drm_property_blob *blob; + struct drm_color_ctm_3x4 *ctm; + + ctm = kunit_kzalloc(test, sizeof(*ctm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctm); + ctm->matrix[0] = 1ULL << 32; /* identity diagonal */ + ctm->matrix[5] = 1ULL << 32; + ctm->matrix[10] = 1ULL << 32; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->data = ctm; + blob->length = sizeof(struct drm_color_ctm_3x4); + f.colorop_state->data = blob; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_3x4_matrix(f.plane_state, f.dc_plane_state, f.colorop), + 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->gamut_remap_matrix.enable_remap); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->input_csc_color_matrix.enable_adjustment); +} + +/** + * dm_test_colorop_3x4_matrix_bad_length - Wrong blob length: must return -EINVAL + * @test: KUnit test context + */ +static void dm_test_colorop_3x4_matrix_bad_length(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_CTM_3X4); + struct drm_property_blob *blob; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->data = kunit_kzalloc(test, 8, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob->data); + blob->length = 7; /* not sizeof(struct drm_color_ctm_3x4) */ + f.colorop_state->data = blob; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_3x4_matrix(f.plane_state, f.dc_plane_state, f.colorop), + -EINVAL); +} + +/** + * dm_test_colorop_degamma_predefined - Degamma 1D curve programs predefined TF + * @test: KUnit test context + */ +static void dm_test_colorop_degamma_predefined(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_1D_CURVE); + + /* SRGB_EOTF is part of amdgpu_dm_supported_degam_tfs */ + f.colorop_state->curve_1d_type = DRM_COLOROP_1D_CURVE_SRGB_EOTF; + f.colorop_state->bypass = false; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_degamma(f.plane_state, f.dc_plane_state, f.colorop), + 0); + KUNIT_EXPECT_EQ(test, + (int)f.dc_plane_state->in_transfer_func.type, + (int)TF_TYPE_PREDEFINED); + KUNIT_EXPECT_EQ(test, + (int)f.dc_plane_state->in_transfer_func.tf, + (int)TRANSFER_FUNCTION_SRGB); +} + +/** + * dm_test_colorop_degamma_no_match - Unsupported degamma curve: must return -EINVAL + * @test: KUnit test context + */ +static void dm_test_colorop_degamma_no_match(struct kunit *test) +{ + struct dm_test_colorop_fixture f = + dm_test_colorop_setup(test, DRM_COLOROP_1D_CURVE); + + /* SRGB_INV_EOTF is a shaper TF, not in amdgpu_dm_supported_degam_tfs */ + f.colorop_state->curve_1d_type = DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF; + + KUNIT_EXPECT_EQ(test, + __set_dm_plane_colorop_degamma(f.plane_state, f.dc_plane_state, f.colorop), + -EINVAL); +} + static struct kunit_case dm_color_test_cases[] = { /* amdgpu_dm_fixpt_from_s3132 */ KUNIT_CASE(dm_test_fixpt_from_s3132_zero), @@ -1747,6 +1945,15 @@ static struct kunit_case dm_color_test_cases[] = { KUNIT_CASE(dm_test_verify_lut3d_bad_shaper), KUNIT_CASE(dm_test_verify_lut3d_bad_lut3d), KUNIT_CASE(dm_test_verify_lut3d_valid), + /* __set_dm_plane_colorop_multiplier */ + KUNIT_CASE(dm_test_colorop_multiplier_applied), + KUNIT_CASE(dm_test_colorop_multiplier_no_match), + /* __set_dm_plane_colorop_3x4_matrix */ + KUNIT_CASE(dm_test_colorop_3x4_matrix_applied), + KUNIT_CASE(dm_test_colorop_3x4_matrix_bad_length), + /* __set_dm_plane_colorop_degamma */ + KUNIT_CASE(dm_test_colorop_degamma_predefined), + KUNIT_CASE(dm_test_colorop_degamma_no_match), {} }; -- cgit v1.2.3 From 32604ff2204a8ec3b9d06b09006846259129f0b3 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 30 Jun 2026 13:10:07 -0600 Subject: drm/amd/display: Test CRTC color management update [WHAT] Add KUnit coverage for amdgpu_dm_check_crtc_color_mgmt() and amdgpu_dm_update_crtc_color_mgmt(). A shared color-update fixture (adev/DC, atomic state, CRTC/stream and plane state) backs the tests: the check path succeeds with no CRTC LUTs, and the update path leaves the stream gamut remap bypassed without a CTM and enables it when a CRTC CTM is present. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 2 + .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 142 +++++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index be0925c4e3e5..d6cba86f51e8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -1323,6 +1323,7 @@ int amdgpu_dm_check_crtc_color_mgmt(struct dm_crtc_state *crtc, return r; } +EXPORT_IF_KUNIT(amdgpu_dm_check_crtc_color_mgmt); /** * amdgpu_dm_update_crtc_color_mgmt: Maps DRM color management to DC stream. @@ -1381,6 +1382,7 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_update_crtc_color_mgmt); static int map_crtc_degamma_to_dc_plane(struct dm_crtc_state *crtc, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index ec0dd0025ae6..30f46ddc060a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -1841,6 +1841,144 @@ static void dm_test_colorop_degamma_no_match(struct kunit *test) -EINVAL); } +/* ---- Tests for CRTC and plane color management update paths ---- */ + +/** + * struct dm_test_color_update_fixture - minimal color update fixture + * @adev: backing amdgpu device + * @state: DRM atomic state with @adev's DRM device + * @crtc_state: DM CRTC state under test + * @stream: DC stream referenced by @crtc_state + * @dm_plane_state: DM plane state under test + * @plane: DRM plane referenced by @dm_plane_state + * @dc_plane_state: DC plane state under test + */ +struct dm_test_color_update_fixture { + struct amdgpu_device *adev; + struct drm_atomic_commit *state; + struct dm_crtc_state *crtc_state; + struct dc_stream_state *stream; + struct dm_plane_state *dm_plane_state; + struct drm_plane *plane; + struct dc_plane_state *dc_plane_state; +}; + +/** + * dm_test_color_update_setup - allocate a minimal color update fixture + * @test: KUnit test context + * + * Returns: a populated fixture with all large DC/DRM state heap-allocated. + */ +static struct dm_test_color_update_fixture +dm_test_color_update_setup(struct kunit *test) +{ + struct dm_test_color_update_fixture f = {0}; + + f.adev = dm_kunit_alloc_adev(test); + f.adev->dm.dc = kunit_kzalloc(test, sizeof(*f.adev->dm.dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.adev->dm.dc); + + f.state = kunit_kzalloc(test, sizeof(*f.state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.state); + f.state->dev = &f.adev->ddev; + + f.stream = kunit_kzalloc(test, sizeof(*f.stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.stream); + + f.crtc_state = kunit_kzalloc(test, sizeof(*f.crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.crtc_state); + f.crtc_state->base.state = f.state; + f.crtc_state->stream = f.stream; + + f.plane = kunit_kzalloc(test, sizeof(*f.plane), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.plane); + f.plane->dev = &f.adev->ddev; + + f.dm_plane_state = kunit_kzalloc(test, sizeof(*f.dm_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.dm_plane_state); + f.dm_plane_state->base.state = f.state; + f.dm_plane_state->base.plane = f.plane; + + f.dc_plane_state = kunit_kzalloc(test, sizeof(*f.dc_plane_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f.dc_plane_state); + + return f; +} + +/** + * dm_test_make_ctm_blob - Allocate a fake drm_property_blob for CTM data + * @test: KUnit test context + * @data: CTM data pointer + * @size: CTM data size in bytes + * + * Returns: a fake property blob pointing at @data. + */ +static struct drm_property_blob * +dm_test_make_ctm_blob(struct kunit *test, void *data, size_t size) +{ + struct drm_property_blob *blob; + + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->data = data; + blob->length = size; + + return blob; +} + +/** + * dm_test_check_crtc_color_mgmt_no_luts - No CRTC LUTs check succeeds + * @test: KUnit test context + */ +static void dm_test_check_crtc_color_mgmt_no_luts(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.crtc_state->cm_has_degamma = true; + f.crtc_state->cm_is_degamma_srgb = true; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_check_crtc_color_mgmt(f.crtc_state, true), 0); + KUNIT_EXPECT_FALSE(test, f.crtc_state->cm_has_degamma); + KUNIT_EXPECT_FALSE(test, f.crtc_state->cm_is_degamma_srgb); +} + +/** + * dm_test_update_crtc_color_mgmt_no_ctm - No CRTC CTM leaves remap bypassed + * @test: KUnit test context + */ +static void dm_test_update_crtc_color_mgmt_no_ctm(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.stream->gamut_remap_matrix.enable_remap = true; + f.stream->csc_color_matrix.enable_adjustment = true; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_update_crtc_color_mgmt(f.crtc_state), 0); + KUNIT_EXPECT_FALSE(test, f.stream->gamut_remap_matrix.enable_remap); + KUNIT_EXPECT_FALSE(test, f.stream->csc_color_matrix.enable_adjustment); +} + +/** + * dm_test_update_crtc_color_mgmt_ctm - CRTC CTM enables stream gamut remap + * @test: KUnit test context + */ +static void dm_test_update_crtc_color_mgmt_ctm(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_color_ctm *ctm; + + ctm = kunit_kzalloc(test, sizeof(*ctm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctm); + ctm->matrix[0] = 1ULL << 32; + ctm->matrix[4] = 1ULL << 32; + ctm->matrix[8] = 1ULL << 32; + f.crtc_state->base.ctm = dm_test_make_ctm_blob(test, ctm, sizeof(*ctm)); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_update_crtc_color_mgmt(f.crtc_state), 0); + KUNIT_EXPECT_TRUE(test, f.stream->gamut_remap_matrix.enable_remap); + KUNIT_EXPECT_FALSE(test, f.stream->csc_color_matrix.enable_adjustment); +} + static struct kunit_case dm_color_test_cases[] = { /* amdgpu_dm_fixpt_from_s3132 */ KUNIT_CASE(dm_test_fixpt_from_s3132_zero), @@ -1954,6 +2092,10 @@ static struct kunit_case dm_color_test_cases[] = { /* __set_dm_plane_colorop_degamma */ KUNIT_CASE(dm_test_colorop_degamma_predefined), KUNIT_CASE(dm_test_colorop_degamma_no_match), + /* CRTC and plane color management update paths */ + KUNIT_CASE(dm_test_check_crtc_color_mgmt_no_luts), + KUNIT_CASE(dm_test_update_crtc_color_mgmt_no_ctm), + KUNIT_CASE(dm_test_update_crtc_color_mgmt_ctm), {} }; -- cgit v1.2.3 From fc3478d865a297fe2b78e3f219364d859a3083e5 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 30 Jun 2026 13:10:33 -0600 Subject: drm/amd/display: Test plane color management update [WHAT] Add KUnit coverage for the legacy paths of amdgpu_dm_update_plane_color_mgmt(). Using the shared color-update fixture, the tests cover the bad 3D LUT size early failure, the default fallback path, implicit CRTC degamma mapping, color caps taken from the DC context, and a plane CTM mapping to the DPP gamut remap matrix. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 108 +++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index d6cba86f51e8..658dfc37d4d4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -2132,3 +2132,4 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, return amdgpu_dm_plane_set_color_properties(plane_state, dc_plane_state); } +EXPORT_IF_KUNIT(amdgpu_dm_update_plane_color_mgmt); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index 30f46ddc060a..e0e659dceafe 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -1979,6 +1979,109 @@ static void dm_test_update_crtc_color_mgmt_ctm(struct kunit *test) KUNIT_EXPECT_FALSE(test, f.stream->csc_color_matrix.enable_adjustment); } +/** + * dm_test_update_plane_color_mgmt_bad_lut3d - Bad 3D LUT size fails early + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_bad_lut3d(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->lut3d = dm_test_make_lut_blob(test, 128); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, + f.dc_plane_state), + -EINVAL); +} + +/** + * dm_test_update_plane_color_mgmt_fallback_defaults - Legacy fallback default path + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_fallback_defaults(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_EQ(test, f.dc_plane_state->hdr_mult.value, 0LL); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->gamut_remap_matrix.enable_remap); +} + +/** + * dm_test_update_plane_color_mgmt_maps_crtc_degamma - CRTC implicit degamma path + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_maps_crtc_degamma(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.crtc_state->cm_is_degamma_srgb = true; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_EQ(test, + (int)f.dc_plane_state->in_transfer_func.type, + (int)TF_TYPE_PREDEFINED); + KUNIT_EXPECT_EQ(test, + (int)f.dc_plane_state->in_transfer_func.tf, + (int)TRANSFER_FUNCTION_SRGB); +} + +/** + * dm_test_update_plane_color_mgmt_uses_color_caps - DC context provides color caps + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_uses_color_caps(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct dc_context *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->dc = f.adev->dm.dc; + f.dc_plane_state->ctx = ctx; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); +} + +/** + * dm_test_update_plane_color_mgmt_plane_ctm - Plane CTM maps to gamut remap + * @test: KUnit test context + * + * When the plane has a CTM blob, the update path programs the DPP gamut + * remap matrix and enables remapping (and disables the input CSC). + */ +static void dm_test_update_plane_color_mgmt_plane_ctm(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_color_ctm_3x4 *ctm; + + ctm = kunit_kzalloc(test, sizeof(*ctm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctm); + /* Identity-ish entries; values are irrelevant to the branch taken. */ + ctm->matrix[0] = 0x100000000ULL; + ctm->matrix[5] = 0x100000000ULL; + ctm->matrix[10] = 0x100000000ULL; + + f.dm_plane_state->ctm = dm_test_make_ctm_blob(test, ctm, sizeof(*ctm)); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->gamut_remap_matrix.enable_remap); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->input_csc_color_matrix.enable_adjustment); +} + static struct kunit_case dm_color_test_cases[] = { /* amdgpu_dm_fixpt_from_s3132 */ KUNIT_CASE(dm_test_fixpt_from_s3132_zero), @@ -2096,6 +2199,11 @@ static struct kunit_case dm_color_test_cases[] = { KUNIT_CASE(dm_test_check_crtc_color_mgmt_no_luts), KUNIT_CASE(dm_test_update_crtc_color_mgmt_no_ctm), KUNIT_CASE(dm_test_update_crtc_color_mgmt_ctm), + KUNIT_CASE(dm_test_update_plane_color_mgmt_bad_lut3d), + KUNIT_CASE(dm_test_update_plane_color_mgmt_fallback_defaults), + KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_crtc_degamma), + KUNIT_CASE(dm_test_update_plane_color_mgmt_uses_color_caps), + KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_ctm), {} }; -- cgit v1.2.3 From 390a56d812fd499a8e8bc3ee29da4be959a03753 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 30 Jun 2026 13:11:01 -0600 Subject: drm/amd/display: Test plane colorop pipeline update [WHAT] Add KUnit coverage for the colorop-pipeline path of amdgpu_dm_update_plane_color_mgmt(). Test-local helpers fabricate a linked colorop pipeline so the update path can be exercised end to end: a fully bypassed pipeline succeeds, a pipeline missing the multiplier or the 3x4 op falls back to defaults, and a pipeline with no 3D LUT hardware skips the 3D ops. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 219 +++++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index e0e659dceafe..e76e533a8323 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -2082,6 +2082,221 @@ static void dm_test_update_plane_color_mgmt_plane_ctm(struct kunit *test) KUNIT_EXPECT_FALSE(test, f.dc_plane_state->input_csc_color_matrix.enable_adjustment); } +/** + * dm_test_colorop_pipeline_add - append one colorop to a fabricated pipeline + * @test: KUnit test context + * @f: color update fixture that owns the atomic state + * @index: colorop array index to populate + * @type: colorop type + * @curve_1d_type: 1D curve type for DRM_COLOROP_1D_CURVE states + * @bypass: bypass flag for the new colorop state + * + * Returns: the newly allocated colorop. + */ +static struct drm_colorop * +dm_test_colorop_pipeline_add(struct kunit *test, + struct dm_test_color_update_fixture *f, + int index, enum drm_colorop_type type, + enum drm_colorop_curve_1d_type curve_1d_type, + bool bypass) +{ + struct drm_colorop_state *colorop_state; + struct drm_colorop *colorop; + + colorop = kunit_kzalloc(test, sizeof(*colorop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorop); + colorop->dev = &f->adev->ddev; + colorop->type = type; + colorop->size = MAX_COLOR_LUT_ENTRIES; + + colorop_state = kunit_kzalloc(test, sizeof(*colorop_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorop_state); + colorop_state->colorop = colorop; + colorop_state->curve_1d_type = curve_1d_type; + colorop_state->bypass = bypass; + + f->state->colorops[index].ptr = colorop; + f->state->colorops[index].new_state = colorop_state; + + return colorop; +} + +/** + * dm_test_colorop_pipeline_setup - build a linked colorop pipeline prefix + * @test: KUnit test context + * @f: color update fixture that owns the atomic state + * @types: colorop types to create + * @curves: curve type for each colorop state + * @bypass: bypass flag for each colorop state + * @count: number of colorops to create + * + * Returns: the first colorop in the linked pipeline. + */ +static struct drm_colorop * +dm_test_colorop_pipeline_setup(struct kunit *test, + struct dm_test_color_update_fixture *f, + const enum drm_colorop_type *types, + const enum drm_colorop_curve_1d_type *curves, + const bool *bypass, + int count) +{ + struct drm_colorop **colorops; + int i; + + f->state->colorops = kunit_kcalloc(test, count, sizeof(*f->state->colorops), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, f->state->colorops); + f->adev->ddev.mode_config.num_colorop = count; + + colorops = kunit_kcalloc(test, count, sizeof(*colorops), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, colorops); + + for (i = 0; i < count; i++) + colorops[i] = dm_test_colorop_pipeline_add(test, f, i, types[i], curves[i], bypass[i]); + + for (i = 0; i < count - 1; i++) + colorops[i]->next = colorops[i + 1]; + + return colorops[0]; +} + +/** + * dm_test_update_plane_color_mgmt_colorop_bypass_pipeline - bypassed pipeline succeeds + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_bypass_pipeline(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, true, true, true, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, ARRAY_SIZE(types)); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_missing_multiplier - missing second op falls back + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_missing_multiplier(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { DRM_COLOROP_1D_CURVE }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { true }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, ARRAY_SIZE(types)); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_missing_3x4 - missing third op falls back + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_missing_3x4(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { true, true }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, ARRAY_SIZE(types)); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, f.dc_plane_state), + 0); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw - no 3D LUT skips 3D ops + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, true, true, false, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, + ARRAY_SIZE(types)); + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = false; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_update_plane_color_mgmt(f.crtc_state, &f.dm_plane_state->base, + f.dc_plane_state), + 0); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); +} + static struct kunit_case dm_color_test_cases[] = { /* amdgpu_dm_fixpt_from_s3132 */ KUNIT_CASE(dm_test_fixpt_from_s3132_zero), @@ -2204,6 +2419,10 @@ static struct kunit_case dm_color_test_cases[] = { KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_crtc_degamma), KUNIT_CASE(dm_test_update_plane_color_mgmt_uses_color_caps), KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_ctm), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_bypass_pipeline), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_multiplier), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_3x4), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw), {} }; -- cgit v1.2.3 From 02b3e22145af04b2f3538051bc6a57b7af9a9025 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 19:23:04 -0600 Subject: drm/amd/display: add KUnit tests for DM IP-block callbacks [WHAT] Add KUnit coverage for the simple amdgpu_dm IP-block callbacks (is_idle, wait_for_idle, soft_reset, set_clockgating_state, set_powergating_state and the bandwidth_update display hook) by asserting their placeholder return values. Also add the shared test include block used by the amdgpu_dm test suite. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 22 ++++--- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 9 +++ .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 73 ++++++++++++++++++++++ 3 files changed, 96 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index b1b107907066..07c1e12fa02a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -224,23 +224,26 @@ static int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, return 0; } -static bool dm_is_idle(struct amdgpu_ip_block *ip_block) +STATIC_IFN_KUNIT bool dm_is_idle(struct amdgpu_ip_block *ip_block) { /* XXX todo */ return true; } +EXPORT_IF_KUNIT(dm_is_idle); -static int dm_wait_for_idle(struct amdgpu_ip_block *ip_block) +STATIC_IFN_KUNIT int dm_wait_for_idle(struct amdgpu_ip_block *ip_block) { /* XXX todo */ return 0; } +EXPORT_IF_KUNIT(dm_wait_for_idle); -static int dm_soft_reset(struct amdgpu_ip_block *ip_block) +STATIC_IFN_KUNIT int dm_soft_reset(struct amdgpu_ip_block *ip_block) { /* XXX todo */ return 0; } +EXPORT_IF_KUNIT(dm_soft_reset); STATIC_IFN_KUNIT bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, struct dm_crtc_state *new_state) @@ -310,17 +313,19 @@ static inline bool update_planes_and_stream_adapter(struct dc *dc, stream_update); } -static int dm_set_clockgating_state(struct amdgpu_ip_block *ip_block, - enum amd_clockgating_state state) +STATIC_IFN_KUNIT int dm_set_clockgating_state(struct amdgpu_ip_block *ip_block, + enum amd_clockgating_state state) { return 0; } +EXPORT_IF_KUNIT(dm_set_clockgating_state); -static int dm_set_powergating_state(struct amdgpu_ip_block *ip_block, - enum amd_powergating_state state) +STATIC_IFN_KUNIT int dm_set_powergating_state(struct amdgpu_ip_block *ip_block, + enum amd_powergating_state state) { return 0; } +EXPORT_IF_KUNIT(dm_set_powergating_state); /* Prototypes of private functions */ static int dm_early_init(struct amdgpu_ip_block *ip_block); @@ -2797,10 +2802,11 @@ static void amdgpu_dm_destroy_drm_device(struct amdgpu_display_manager *dm) * * Calculate and program the display watermarks and line buffer allocation. */ -static void dm_bandwidth_update(struct amdgpu_device *adev) +STATIC_IFN_KUNIT void dm_bandwidth_update(struct amdgpu_device *adev) { /* TODO: implement later */ } +EXPORT_IF_KUNIT(dm_bandwidth_update); static const struct amdgpu_display_funcs dm_display_funcs = { .bandwidth_update = dm_bandwidth_update, /* called unconditionally */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index f396803d1485..9b8e39fbce57 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1140,6 +1140,15 @@ void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, struct dc_sink *sink); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct amdgpu_ip_block; +bool dm_is_idle(struct amdgpu_ip_block *ip_block); +int dm_wait_for_idle(struct amdgpu_ip_block *ip_block); +int dm_soft_reset(struct amdgpu_ip_block *ip_block); +int dm_set_clockgating_state(struct amdgpu_ip_block *ip_block, + enum amd_clockgating_state state); +int dm_set_powergating_state(struct amdgpu_ip_block *ip_block, + enum amd_powergating_state state); +void dm_bandwidth_update(struct amdgpu_device *adev); int dm_plane_layer_index_cmp(const void *a, const void *b); int fill_plane_color_attributes(const struct drm_plane_state *plane_state, const enum surface_pixel_format format, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 0b29bf0a7d04..d4e37580316f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -6,10 +6,76 @@ */ #include +#include +#include +#include +#include +#include +#include #include "dc.h" +#include "inc/core_types.h" +#include "amd_shared.h" +#include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_kunit_test_helpers.h" + +/* Tests for simple DM callbacks */ + +/** + * dm_test_is_idle - Test placeholder idle callback returns true + * @test: The KUnit test context + */ +static void dm_test_is_idle(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, dm_is_idle(NULL)); +} + +/** + * dm_test_wait_for_idle - Test placeholder wait-for-idle callback returns success + * @test: The KUnit test context + */ +static void dm_test_wait_for_idle(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_wait_for_idle(NULL), 0); +} + +/** + * dm_test_soft_reset - Test placeholder soft-reset callback returns success + * @test: The KUnit test context + */ +static void dm_test_soft_reset(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_soft_reset(NULL), 0); +} + +/** + * dm_test_set_clockgating_state - Test placeholder clockgating callback returns success + * @test: The KUnit test context + */ +static void dm_test_set_clockgating_state(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_set_clockgating_state(NULL, AMD_CG_STATE_GATE), 0); +} + +/** + * dm_test_set_powergating_state - Test placeholder powergating callback returns success + * @test: The KUnit test context + */ +static void dm_test_set_powergating_state(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_set_powergating_state(NULL, AMD_PG_STATE_GATE), 0); +} + +/** + * dm_test_bandwidth_update - Test placeholder bandwidth update is callable + * @test: The KUnit test context + */ +static void dm_test_bandwidth_update(struct kunit *test) +{ + dm_bandwidth_update(NULL); +} /* Tests for dm_plane_layer_index_cmp() */ @@ -884,6 +950,13 @@ static void dm_test_master_stream_defaults_to_first(struct kunit *test) } static struct kunit_case amdgpu_dm_tests[] = { + /* Simple DM callbacks */ + KUNIT_CASE(dm_test_is_idle), + KUNIT_CASE(dm_test_wait_for_idle), + KUNIT_CASE(dm_test_soft_reset), + KUNIT_CASE(dm_test_set_clockgating_state), + KUNIT_CASE(dm_test_set_powergating_state), + KUNIT_CASE(dm_test_bandwidth_update), /* dm_plane_layer_index_cmp */ KUNIT_CASE(dm_test_plane_layer_index_cmp_equal), KUNIT_CASE(dm_test_plane_layer_index_cmp_descending), -- cgit v1.2.3 From 9e5b9ead759f5dd4321b719a6b9a03e233e1b908 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 19:23:04 -0600 Subject: drm/amd/display: add KUnit tests for DM CRTC vblank/scanout [WHAT] Add KUnit tests for the DM CRTC helpers: the no-writeback and non-pending writeback paths of amdgpu_dm_crtc_complete_writeback, the out-of-range and no-stream paths of dm_vblank_get_counter, and the invalid-CRTC and no-stream paths of dm_crtc_get_scanoutpos. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 9 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 + .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 112 +++++++++++++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 07c1e12fa02a..dbce7f6ac21d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -169,7 +169,7 @@ static inline void amdgpu_dm_exit_ips_for_hw_access(struct dc *dc) * @return * Counter for vertical blanks */ -static u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc) +STATIC_IFN_KUNIT u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc) { struct amdgpu_crtc *acrtc = NULL; @@ -186,9 +186,10 @@ static u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc) return dc_stream_get_vblank_counter(acrtc->dm_irq_params.stream); } +EXPORT_IF_KUNIT(dm_vblank_get_counter); -static int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, - u32 *vbl, u32 *position) +STATIC_IFN_KUNIT int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, + u32 *vbl, u32 *position) { u32 v_blank_start = 0, v_blank_end = 0, h_position = 0, v_position = 0; struct amdgpu_crtc *acrtc = NULL; @@ -223,6 +224,7 @@ static int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, return 0; } +EXPORT_IF_KUNIT(dm_crtc_get_scanoutpos); STATIC_IFN_KUNIT bool dm_is_idle(struct amdgpu_ip_block *ip_block) { @@ -4688,6 +4690,7 @@ bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc) return true; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_complete_writeback); static void dm_clear_writeback(struct amdgpu_display_manager *dm, struct amdgpu_crtc *acrtc, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 9b8e39fbce57..1c2cb0332fa3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1149,6 +1149,9 @@ int dm_set_clockgating_state(struct amdgpu_ip_block *ip_block, int dm_set_powergating_state(struct amdgpu_ip_block *ip_block, enum amd_powergating_state state); void dm_bandwidth_update(struct amdgpu_device *adev); +u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc); +int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, + u32 *vbl, u32 *position); int dm_plane_layer_index_cmp(const void *a, const void *b); int fill_plane_color_attributes(const struct drm_plane_state *plane_state, const enum surface_pixel_format format, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index d4e37580316f..7b92078d95bc 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -77,6 +77,112 @@ static void dm_test_bandwidth_update(struct kunit *test) dm_bandwidth_update(NULL); } +/** + * dm_test_crtc_complete_writeback_no_connector - Test no writeback connector returns false + * @test: The KUnit test context + */ +static void dm_test_crtc_complete_writeback_no_connector(struct kunit *test) +{ + struct amdgpu_crtc *acrtc; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_complete_writeback(acrtc)); +} + +/** + * dm_test_crtc_complete_writeback_not_pending - Test non-pending writeback returns false + * @test: The KUnit test context + */ +static void dm_test_crtc_complete_writeback_not_pending(struct kunit *test) +{ + struct amdgpu_crtc *acrtc; + struct drm_writeback_connector *wb_conn; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + wb_conn = kunit_kzalloc(test, sizeof(*wb_conn), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, wb_conn); + + spin_lock_init(&wb_conn->job_lock); + acrtc->wb_conn = wb_conn; + acrtc->wb_pending = false; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_complete_writeback(acrtc)); +} + +/** + * dm_test_vblank_get_counter_out_of_range - Test out-of-range CRTC returns zero + * @test: The KUnit test context + */ +static void dm_test_vblank_get_counter_out_of_range(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + + adev->mode_info.num_crtc = 1; + + KUNIT_EXPECT_EQ(test, dm_vblank_get_counter(adev, 1), 0U); +} + +/** + * dm_test_vblank_get_counter_no_stream - Test missing stream returns zero + * @test: The KUnit test context + */ +static void dm_test_vblank_get_counter_no_stream(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + adev->mode_info.num_crtc = 1; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, dm_vblank_get_counter(adev, 0), 0U); +} + +/** + * dm_test_crtc_get_scanoutpos_invalid_crtc - Test invalid CRTC returns -EINVAL + * @test: The KUnit test context + */ +static void dm_test_crtc_get_scanoutpos_invalid_crtc(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + u32 vbl = 0; + u32 position = 0; + + adev->mode_info.num_crtc = 1; + + KUNIT_EXPECT_EQ(test, dm_crtc_get_scanoutpos(adev, -1, &vbl, &position), + -EINVAL); + KUNIT_EXPECT_EQ(test, dm_crtc_get_scanoutpos(adev, 1, &vbl, &position), + -EINVAL); +} + +/** + * dm_test_crtc_get_scanoutpos_no_stream - Test missing stream returns zero + * @test: The KUnit test context + */ +static void dm_test_crtc_get_scanoutpos_no_stream(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc; + u32 vbl = 0; + u32 position = 0; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + adev->mode_info.num_crtc = 1; + adev->mode_info.crtcs[0] = acrtc; + + KUNIT_EXPECT_EQ(test, dm_crtc_get_scanoutpos(adev, 0, &vbl, &position), 0); + KUNIT_EXPECT_EQ(test, vbl, 0U); + KUNIT_EXPECT_EQ(test, position, 0U); +} + /* Tests for dm_plane_layer_index_cmp() */ /** @@ -957,6 +1063,12 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_set_clockgating_state), KUNIT_CASE(dm_test_set_powergating_state), KUNIT_CASE(dm_test_bandwidth_update), + KUNIT_CASE(dm_test_crtc_complete_writeback_no_connector), + KUNIT_CASE(dm_test_crtc_complete_writeback_not_pending), + KUNIT_CASE(dm_test_vblank_get_counter_out_of_range), + KUNIT_CASE(dm_test_vblank_get_counter_no_stream), + KUNIT_CASE(dm_test_crtc_get_scanoutpos_invalid_crtc), + KUNIT_CASE(dm_test_crtc_get_scanoutpos_no_stream), /* dm_plane_layer_index_cmp */ KUNIT_CASE(dm_test_plane_layer_index_cmp_equal), KUNIT_CASE(dm_test_plane_layer_index_cmp_descending), -- cgit v1.2.3 From 1b3a4c2b17f5683ebbea3ecf8969b542ca5fe9e2 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 19:23:04 -0600 Subject: drm/amd/display: add KUnit tests for DM atomic state helpers [WHAT] Add KUnit tests for the DM atomic private-state accessors (dm_atomic_get_new_state, dm_atomic_destroy_state), the native cursor-mode selector (dm_should_update_native_cursor) and amdgpu_dm_smu_write_watermarks_table. Cover the empty and matching private-object lookups, the NULL-context destroy path, the NULL, native and overlay cursor-mode paths, and the non-Navi1x watermark table early return. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 20 +-- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 8 ++ .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 155 +++++++++++++++++++++ 3 files changed, 175 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index dbce7f6ac21d..82d445316e82 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1404,7 +1404,7 @@ static void s3_handle_mst(struct drm_device *dev, bool suspend) drm_connector_list_iter_end(&iter); } -static int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev) +STATIC_IFN_KUNIT int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev) { int ret = 0; @@ -1454,6 +1454,7 @@ static int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_smu_write_watermarks_table); static int dm_oem_i2c_hw_init(struct amdgpu_device *adev) { @@ -2284,7 +2285,7 @@ int dm_atomic_get_state(struct drm_atomic_commit *state, return 0; } -static struct dm_atomic_state * +STATIC_IFN_KUNIT struct dm_atomic_state * dm_atomic_get_new_state(struct drm_atomic_commit *state) { struct drm_device *dev = state->dev; @@ -2301,6 +2302,7 @@ dm_atomic_get_new_state(struct drm_atomic_commit *state) return NULL; } +EXPORT_IF_KUNIT(dm_atomic_get_new_state); static struct drm_private_state * dm_atomic_duplicate_state(struct drm_private_obj *obj) @@ -2326,8 +2328,8 @@ dm_atomic_duplicate_state(struct drm_private_obj *obj) return &new_state->base; } -static void dm_atomic_destroy_state(struct drm_private_obj *obj, - struct drm_private_state *state) +STATIC_IFN_KUNIT void dm_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) { struct dm_atomic_state *dm_state = to_dm_atomic_state(state); @@ -2336,6 +2338,7 @@ static void dm_atomic_destroy_state(struct drm_private_obj *obj, kfree(dm_state); } +EXPORT_IF_KUNIT(dm_atomic_destroy_state); static struct drm_private_state * dm_atomic_create_state(struct drm_private_obj *obj) @@ -6363,10 +6366,10 @@ static int dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc, return 0; } -static bool dm_should_update_native_cursor(struct drm_atomic_commit *state, - struct drm_crtc *old_plane_crtc, - struct drm_crtc *new_plane_crtc, - bool enable) +STATIC_IFN_KUNIT bool dm_should_update_native_cursor(struct drm_atomic_commit *state, + struct drm_crtc *old_plane_crtc, + struct drm_crtc *new_plane_crtc, + bool enable) { struct drm_crtc_state *old_crtc_state, *new_crtc_state; struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state; @@ -6391,6 +6394,7 @@ static bool dm_should_update_native_cursor(struct drm_atomic_commit *state, return dm_new_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE; } } +EXPORT_IF_KUNIT(dm_should_update_native_cursor); static int dm_update_plane_state(struct dc *dc, struct drm_atomic_commit *state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 1c2cb0332fa3..0a3a8fbbd6fb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1152,6 +1152,14 @@ void dm_bandwidth_update(struct amdgpu_device *adev); u32 dm_vblank_get_counter(struct amdgpu_device *adev, int crtc); int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, u32 *vbl, u32 *position); +struct dm_atomic_state *dm_atomic_get_new_state(struct drm_atomic_commit *state); +void dm_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state); +int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev); +bool dm_should_update_native_cursor(struct drm_atomic_commit *state, + struct drm_crtc *old_plane_crtc, + struct drm_crtc *new_plane_crtc, + bool enable); int dm_plane_layer_index_cmp(const void *a, const void *b); int fill_plane_color_attributes(const struct drm_plane_state *plane_state, const enum surface_pixel_format format, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 7b92078d95bc..f2ec4a13b2a8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -183,6 +183,154 @@ static void dm_test_crtc_get_scanoutpos_no_stream(struct kunit *test) KUNIT_EXPECT_EQ(test, position, 0U); } +/** + * dm_test_atomic_get_new_state_empty - Test empty atomic state has no DM state + * @test: The KUnit test context + */ +static void dm_test_atomic_get_new_state_empty(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + state->dev = &adev->ddev; + + KUNIT_EXPECT_NULL(test, dm_atomic_get_new_state(state)); +} + +/** + * dm_test_atomic_get_new_state_match - Test atomic state returns matching DM private state + * @test: The KUnit test context + */ +static void dm_test_atomic_get_new_state_match(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dm_atomic_state *dm_state; + struct drm_atomic_commit *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + state->private_objs = kunit_kzalloc(test, sizeof(*state->private_objs), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->private_objs); + + state->dev = &adev->ddev; + state->num_private_objs = 1; + state->private_objs[0].ptr = &adev->dm.atomic_obj; + state->private_objs[0].new_state = &dm_state->base; + + KUNIT_EXPECT_PTR_EQ(test, dm_atomic_get_new_state(state), dm_state); +} + +/** + * dm_test_should_update_native_cursor_without_crtc - Test NULL crtc cases update native cursor + * @test: The KUnit test context + */ +static void dm_test_should_update_native_cursor_without_crtc(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, dm_should_update_native_cursor(NULL, NULL, NULL, false)); + KUNIT_EXPECT_TRUE(test, dm_should_update_native_cursor(NULL, NULL, NULL, true)); +} + +/** + * dm_test_should_update_native_cursor_disable_native - Test disable path reads old crtc cursor mode + * @test: The KUnit test context + */ +static void dm_test_should_update_native_cursor_disable_native(struct kunit *test) +{ + struct dm_crtc_state *dm_crtc_state; + struct drm_atomic_commit *state; + struct drm_crtc *crtc; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->crtcs); + + crtc->index = 0; + dm_crtc_state->cursor_mode = DM_CURSOR_NATIVE_MODE; + state->crtcs[0].old_state = &dm_crtc_state->base; + + KUNIT_EXPECT_TRUE(test, + dm_should_update_native_cursor(state, crtc, NULL, false)); +} + +/** + * dm_test_should_update_native_cursor_enable_overlay - Test enable path reads new crtc cursor mode + * @test: The KUnit test context + */ +static void dm_test_should_update_native_cursor_enable_overlay(struct kunit *test) +{ + struct dm_crtc_state *dm_crtc_state; + struct drm_atomic_commit *state; + struct drm_crtc *crtc; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->crtcs); + + crtc->index = 0; + dm_crtc_state->cursor_mode = DM_CURSOR_OVERLAY_MODE; + state->crtcs[0].new_state = &dm_crtc_state->base; + + KUNIT_EXPECT_FALSE(test, + dm_should_update_native_cursor(state, NULL, crtc, true)); +} + +/** + * dm_test_atomic_destroy_state_no_context - Test destroying DM atomic state without a DC context + * @test: The KUnit test context + */ +static void dm_test_atomic_destroy_state_no_context(struct kunit *test) +{ + struct dm_atomic_state *dm_state; + + /* + * Use kzalloc(), not kunit_kzalloc(): dm_atomic_destroy_state() frees + * the state itself, so KUnit-managed memory would be double-freed. + */ + dm_state = kzalloc(sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + /* context == NULL: dc_state_release() is skipped and the state is freed. */ + dm_atomic_destroy_state(NULL, &dm_state->base); +} + +/** + * dm_test_smu_write_watermarks_table_default - Test watermarks table skips non-Navi1x IP versions + * @test: The KUnit test context + */ +static void dm_test_smu_write_watermarks_table_default(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + + /* + * A zeroed adev reports DCE IP version 0, which is not one of the + * Navi1x versions handled by the switch, so the function returns early. + */ + KUNIT_EXPECT_EQ(test, amdgpu_dm_smu_write_watermarks_table(adev), 0); +} + /* Tests for dm_plane_layer_index_cmp() */ /** @@ -1069,6 +1217,13 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_vblank_get_counter_no_stream), KUNIT_CASE(dm_test_crtc_get_scanoutpos_invalid_crtc), KUNIT_CASE(dm_test_crtc_get_scanoutpos_no_stream), + KUNIT_CASE(dm_test_atomic_get_new_state_empty), + KUNIT_CASE(dm_test_atomic_get_new_state_match), + KUNIT_CASE(dm_test_should_update_native_cursor_without_crtc), + KUNIT_CASE(dm_test_should_update_native_cursor_disable_native), + KUNIT_CASE(dm_test_should_update_native_cursor_enable_overlay), + KUNIT_CASE(dm_test_atomic_destroy_state_no_context), + KUNIT_CASE(dm_test_smu_write_watermarks_table_default), /* dm_plane_layer_index_cmp */ KUNIT_CASE(dm_test_plane_layer_index_cmp_equal), KUNIT_CASE(dm_test_plane_layer_index_cmp_descending), -- cgit v1.2.3 From 0972949607af8ae4c7ad929d791eed6cc19d1c5f Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 19:23:04 -0600 Subject: drm/amd/display: add KUnit tests for DM stream scaling [WHAT] Add KUnit tests for the stream scaling path: a disable-underscan case for is_scaling_state_different, and coverage of amdgpu_dm_update_stream_scaling_settings across the full-screen default, RMX_FULL, RMX_ASPECT pillarbox/letterbox, RMX_CENTER and underscan paths. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 + .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 231 +++++++++++++++++++++ 2 files changed, 232 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 82d445316e82..3c003b02e729 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3458,6 +3458,7 @@ void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev, dst.x, dst.y, dst.width, dst.height); } +EXPORT_IF_KUNIT(amdgpu_dm_update_stream_scaling_settings); static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_commit *state, struct dc_state *dc_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index f2ec4a13b2a8..af2f43bf2898 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -830,6 +830,28 @@ static void dm_test_scaling_state_underscan_enabled(struct kunit *test) KUNIT_EXPECT_TRUE(test, is_scaling_state_different(new_state, old_state)); } +/** + * dm_test_scaling_state_underscan_disabled - Test disabling underscan with borders differs + * @test: The KUnit test context + */ +static void dm_test_scaling_state_underscan_disabled(struct kunit *test) +{ + struct dm_connector_state *old_state; + struct dm_connector_state *new_state; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_state); + KUNIT_ASSERT_NOT_NULL(test, new_state); + + old_state->underscan_enable = true; + old_state->underscan_hborder = 16; + old_state->underscan_vborder = 16; + new_state->underscan_enable = false; + + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(new_state, old_state)); +} + /** * dm_test_scaling_state_underscan_border_changed - Test changed underscan borders differ * @test: The KUnit test context @@ -1203,6 +1225,206 @@ static void dm_test_master_stream_defaults_to_first(struct kunit *test) stream0); } +/* Tests for amdgpu_dm_update_stream_scaling_settings() */ + +/** + * dm_test_update_scaling_null_mode - Test NULL mode leaves the stream rects untouched + * @test: The KUnit test context + */ +static void dm_test_update_scaling_null_mode(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, NULL, NULL, stream); + + /* NULL mode: early return before touching src/dst */ + KUNIT_EXPECT_EQ(test, stream->src.width, 0); + KUNIT_EXPECT_EQ(test, stream->dst.width, 0); +} + +/** + * dm_test_update_scaling_fullscreen_default - Test full-screen default with no dm_state + * @test: The KUnit test context + */ +static void dm_test_update_scaling_fullscreen_default(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct drm_display_mode mode = { 0 }; + + mode.hdisplay = 1920; + mode.vdisplay = 1080; + stream->timing.h_addressable = 2560; + stream->timing.v_addressable = 1440; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, NULL, stream); + + /* src = mode, dst = timing addressable, no centering without dm_state */ + KUNIT_EXPECT_EQ(test, stream->src.width, 1920); + KUNIT_EXPECT_EQ(test, stream->src.height, 1080); + KUNIT_EXPECT_EQ(test, stream->dst.width, 2560); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1440); + KUNIT_EXPECT_EQ(test, stream->dst.x, 0); + KUNIT_EXPECT_EQ(test, stream->dst.y, 0); +} + +/** + * dm_test_update_scaling_rmx_full - Test RMX_FULL keeps a full-size, centered dst + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_full(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + mode.hdisplay = 1280; + mode.vdisplay = 720; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_FULL; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* RMX_FULL: dst stays full addressable, offset 0 */ + KUNIT_EXPECT_EQ(test, stream->dst.width, 1920); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1080); + KUNIT_EXPECT_EQ(test, stream->dst.x, 0); + KUNIT_EXPECT_EQ(test, stream->dst.y, 0); +} + +/** + * dm_test_update_scaling_rmx_aspect_pillarbox - Test RMX_ASPECT preserves aspect ratio + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_aspect_pillarbox(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + /* 4:3 source on a 16:9 panel -> pillarboxed */ + mode.hdisplay = 1024; + mode.vdisplay = 768; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_ASPECT; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* + * src.width*dst.height (1024*1080) < src.height*dst.width (768*1920): + * width scaled to src.width*dst.height/src.height = 1440, height stays + * 1080, centered horizontally at (1920-1440)/2 = 240. + */ + KUNIT_EXPECT_EQ(test, stream->dst.width, 1440); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1080); + KUNIT_EXPECT_EQ(test, stream->dst.x, 240); + KUNIT_EXPECT_EQ(test, stream->dst.y, 0); +} + +/** + * dm_test_update_scaling_rmx_aspect_letterbox - Test RMX_ASPECT letterboxes wide sources + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_aspect_letterbox(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + /* 16:9 source on a 4:3 panel -> letterboxed */ + mode.hdisplay = 1920; + mode.vdisplay = 1080; + stream->timing.h_addressable = 1024; + stream->timing.v_addressable = 768; + dm_state->scaling = RMX_ASPECT; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + KUNIT_EXPECT_EQ(test, stream->dst.width, 1024); + KUNIT_EXPECT_EQ(test, stream->dst.height, 576); + KUNIT_EXPECT_EQ(test, stream->dst.x, 0); + KUNIT_EXPECT_EQ(test, stream->dst.y, 96); +} + +/** + * dm_test_update_scaling_rmx_center - Test RMX_CENTER centers a 1:1 dst + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_center(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + mode.hdisplay = 1280; + mode.vdisplay = 720; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_CENTER; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* RMX_CENTER: dst = src, centered on the addressable area */ + KUNIT_EXPECT_EQ(test, stream->dst.width, 1280); + KUNIT_EXPECT_EQ(test, stream->dst.height, 720); + KUNIT_EXPECT_EQ(test, stream->dst.x, 320); + KUNIT_EXPECT_EQ(test, stream->dst.y, 180); +} + +/** + * dm_test_update_scaling_underscan - Test underscan borders shrink and offset dst + * @test: The KUnit test context + */ +static void dm_test_update_scaling_underscan(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + mode.hdisplay = 1920; + mode.vdisplay = 1080; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_FULL; + dm_state->underscan_enable = true; + dm_state->underscan_hborder = 64; + dm_state->underscan_vborder = 32; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* Full dst, then underscan: x/y += border/2, width/height -= border */ + KUNIT_EXPECT_EQ(test, stream->dst.x, 32); + KUNIT_EXPECT_EQ(test, stream->dst.y, 16); + KUNIT_EXPECT_EQ(test, stream->dst.width, 1856); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1048); +} + static struct kunit_case amdgpu_dm_tests[] = { /* Simple DM callbacks */ KUNIT_CASE(dm_test_is_idle), @@ -1254,6 +1476,7 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_scaling_state_same), KUNIT_CASE(dm_test_scaling_state_scaling_changed), KUNIT_CASE(dm_test_scaling_state_underscan_enabled), + KUNIT_CASE(dm_test_scaling_state_underscan_disabled), KUNIT_CASE(dm_test_scaling_state_underscan_border_changed), /* is_timing_unchanged_for_freesync */ KUNIT_CASE(dm_test_timing_unchanged_null_args), @@ -1274,6 +1497,14 @@ static struct kunit_case amdgpu_dm_tests[] = { /* set_master_stream */ KUNIT_CASE(dm_test_master_stream_highest_refresh), KUNIT_CASE(dm_test_master_stream_defaults_to_first), + /* amdgpu_dm_update_stream_scaling_settings */ + KUNIT_CASE(dm_test_update_scaling_null_mode), + KUNIT_CASE(dm_test_update_scaling_fullscreen_default), + KUNIT_CASE(dm_test_update_scaling_rmx_full), + KUNIT_CASE(dm_test_update_scaling_rmx_aspect_pillarbox), + KUNIT_CASE(dm_test_update_scaling_rmx_aspect_letterbox), + KUNIT_CASE(dm_test_update_scaling_rmx_center), + KUNIT_CASE(dm_test_update_scaling_underscan), {} }; -- cgit v1.2.3 From bee42eda11acfddf396492c64a4bbcfedff364bf Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 19:23:04 -0600 Subject: drm/amd/display: add KUnit tests for HDCP state diffing [WHAT] Add KUnit tests for is_content_protection_different using a shared connector/crtc fixture. Cover the content-type change, ENABLED->DESIRED re-enable (with and without modeset), UNDESIRED and DESIRED steady states, the update_hdcp hot-plug and stream re-enable paths, and the S3/DESIRED/UNDESIRED enable transitions. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 14 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 7 + .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 237 +++++++++++++++++++++ 3 files changed, 252 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 3c003b02e729..6a39dabdd8d2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3655,12 +3655,13 @@ is_scaling_state_different(const struct dm_connector_state *dm_state, } EXPORT_IF_KUNIT(is_scaling_state_different); -static bool is_content_protection_different(struct drm_crtc_state *new_crtc_state, - struct drm_crtc_state *old_crtc_state, - struct drm_connector_state *new_conn_state, - struct drm_connector_state *old_conn_state, - const struct drm_connector *connector, - struct hdcp_workqueue *hdcp_w) +STATIC_IFN_KUNIT bool +is_content_protection_different(struct drm_crtc_state *new_crtc_state, + struct drm_crtc_state *old_crtc_state, + struct drm_connector_state *new_conn_state, + struct drm_connector_state *old_conn_state, + const struct drm_connector *connector, + struct hdcp_workqueue *hdcp_w) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state); @@ -3774,6 +3775,7 @@ static bool is_content_protection_different(struct drm_crtc_state *new_crtc_stat pr_debug("[HDCP_DM] DESIRED->ENABLED %s :false\n", __func__); return false; } +EXPORT_IF_KUNIT(is_content_protection_different); static void remove_stream(struct amdgpu_device *adev, struct amdgpu_crtc *acrtc, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 0a3a8fbbd6fb..3a2fb5193668 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1178,6 +1178,13 @@ bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, struct dm_crtc_state *new_state); void set_multisync_trigger_params(struct dc_stream_state *stream); void set_master_stream(struct dc_stream_state *stream_set[], int stream_count); +struct hdcp_workqueue; +bool is_content_protection_different(struct drm_crtc_state *new_crtc_state, + struct drm_crtc_state *old_crtc_state, + struct drm_connector_state *new_conn_state, + struct drm_connector_state *old_conn_state, + const struct drm_connector *connector, + struct hdcp_workqueue *hdcp_w); #endif #endif /* __AMDGPU_DM_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index af2f43bf2898..94be7fa28547 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -1425,6 +1425,231 @@ static void dm_test_update_scaling_underscan(struct kunit *test) KUNIT_EXPECT_EQ(test, stream->dst.height, 1048); } +/* Tests for is_content_protection_different() */ + +struct dm_test_cp_ctx { + struct amdgpu_dm_connector *aconnector; + struct dm_connector_state *new_dm; /* also connector->state */ + struct dm_connector_state *old_dm; + struct drm_crtc_state *new_crtc; + struct drm_crtc_state *old_crtc; +}; + +static struct dm_test_cp_ctx *dm_test_cp_ctx_alloc(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->new_dm = kunit_kzalloc(test, sizeof(*ctx->new_dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->new_dm); + ctx->old_dm = kunit_kzalloc(test, sizeof(*ctx->old_dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->old_dm); + ctx->new_crtc = kunit_kzalloc(test, sizeof(*ctx->new_crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->new_crtc); + ctx->old_crtc = kunit_kzalloc(test, sizeof(*ctx->old_crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->old_crtc); + + /* connector->state must be the new dm connector state */ + ctx->aconnector->base.state = &ctx->new_dm->base; + ctx->aconnector->base.dpms = DRM_MODE_DPMS_ON; + + return ctx; +} + +static bool dm_test_cp_diff(struct dm_test_cp_ctx *ctx) +{ + return is_content_protection_different(ctx->new_crtc, ctx->old_crtc, + &ctx->new_dm->base, + &ctx->old_dm->base, + &ctx->aconnector->base, NULL); +} + +/** + * dm_test_cp_diff_hdcp_type_change - Test an HDCP content-type change forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_hdcp_type_change(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.hdcp_content_type = 0; + ctx->new_dm->base.hdcp_content_type = 1; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, + (int)DRM_MODE_CONTENT_PROTECTION_DESIRED); +} + +/** + * dm_test_cp_diff_reenable_mode_changed - Test ENABLED->DESIRED with modeset forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_reenable_mode_changed(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_crtc->mode_changed = true; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, + (int)DRM_MODE_CONTENT_PROTECTION_DESIRED); +} + +/** + * dm_test_cp_diff_reenable_no_change - Test ENABLED->DESIRED without modeset restores ENABLED + * @test: The KUnit test context + */ +static void dm_test_cp_diff_reenable_no_change(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_crtc->mode_changed = false; + + KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, + (int)DRM_MODE_CONTENT_PROTECTION_ENABLED); +} + +/** + * dm_test_cp_diff_undesired - Test UNDESIRED->UNDESIRED needs no update + * @test: The KUnit test context + */ +static void dm_test_cp_diff_undesired(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + + KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); +} + +/** + * dm_test_cp_diff_desired_mode_changed - Test DESIRED->DESIRED with modeset forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_desired_mode_changed(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_crtc->mode_changed = true; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); +} + +/** + * dm_test_cp_diff_desired_no_change - Test steady DESIRED->DESIRED needs no update + * @test: The KUnit test context + */ +static void dm_test_cp_diff_desired_no_change(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_crtc->mode_changed = false; + + KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); +} + +/** + * dm_test_cp_diff_update_hdcp_hotplug - Test the update_hdcp hot-plug path forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_update_hdcp_hotplug(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + struct dc_sink *sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, sink); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->update_hdcp = true; + ctx->aconnector->base.dpms = DRM_MODE_DPMS_ON; + ctx->aconnector->dc_sink = sink; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_FALSE(test, ctx->new_dm->update_hdcp); +} + +/** + * dm_test_cp_diff_stream_reenabled - Test the stream removed-and-re-enabled path forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_stream_reenabled(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + struct drm_crtc *crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, crtc); + crtc->enabled = true; + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->update_hdcp = true; + ctx->old_dm->base.crtc = NULL; + ctx->new_dm->base.crtc = crtc; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_FALSE(test, ctx->new_dm->update_hdcp); +} + +/** + * dm_test_cp_diff_s3_undesired_to_enabled - Test the S3 UNDESIRED->ENABLED path forces true + * @test: The KUnit test context + */ +static void dm_test_cp_diff_s3_undesired_to_enabled(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); + KUNIT_EXPECT_EQ(test, (int)ctx->new_dm->base.content_protection, + (int)DRM_MODE_CONTENT_PROTECTION_DESIRED); +} + +/** + * dm_test_cp_diff_desired_to_enabled - Test DESIRED->ENABLED needs no update + * @test: The KUnit test context + */ +static void dm_test_cp_diff_desired_to_enabled(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + + KUNIT_EXPECT_FALSE(test, dm_test_cp_diff(ctx)); +} + +/** + * dm_test_cp_diff_desired_to_undesired - Test DESIRED->UNDESIRED forces update + * @test: The KUnit test context + */ +static void dm_test_cp_diff_desired_to_undesired(struct kunit *test) +{ + struct dm_test_cp_ctx *ctx = dm_test_cp_ctx_alloc(test); + + ctx->old_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + ctx->new_dm->base.content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + + KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); +} + static struct kunit_case amdgpu_dm_tests[] = { /* Simple DM callbacks */ KUNIT_CASE(dm_test_is_idle), @@ -1505,6 +1730,18 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_update_scaling_rmx_aspect_letterbox), KUNIT_CASE(dm_test_update_scaling_rmx_center), KUNIT_CASE(dm_test_update_scaling_underscan), + /* is_content_protection_different */ + KUNIT_CASE(dm_test_cp_diff_hdcp_type_change), + KUNIT_CASE(dm_test_cp_diff_reenable_mode_changed), + KUNIT_CASE(dm_test_cp_diff_reenable_no_change), + KUNIT_CASE(dm_test_cp_diff_undesired), + KUNIT_CASE(dm_test_cp_diff_desired_mode_changed), + KUNIT_CASE(dm_test_cp_diff_desired_no_change), + KUNIT_CASE(dm_test_cp_diff_update_hdcp_hotplug), + KUNIT_CASE(dm_test_cp_diff_stream_reenabled), + KUNIT_CASE(dm_test_cp_diff_s3_undesired_to_enabled), + KUNIT_CASE(dm_test_cp_diff_desired_to_enabled), + KUNIT_CASE(dm_test_cp_diff_desired_to_undesired), {} }; -- cgit v1.2.3 From b56f2a8c8887e81559098a4d57c92d10190af52b Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 19:23:04 -0600 Subject: drm/amd/display: add KUnit tests for freesync config [WHAT] Add KUnit tests for get_freesync_config_for_crtc and reset_freesync_config_for_crtc using a shared connector/crtc/stream fixture. Cover the writeback early return, the not-capable and out-of-range unsupported paths, the active-variable, inactive and active-fixed states, and the config reset. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 8 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 + .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 197 +++++++++++++++++++++ 3 files changed, 205 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 6a39dabdd8d2..ec08aed843da 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -126,7 +126,7 @@ MODULE_FIRMWARE(FIRMWARE_NAVI12_DMCU); /* basic init/fini API */ static int amdgpu_dm_init(struct amdgpu_device *adev); static void amdgpu_dm_fini(struct amdgpu_device *adev); -static void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); +STATIC_IFN_KUNIT void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); /* * initializes drm_device display related structures, based on the information @@ -5735,7 +5735,7 @@ static int do_aquire_global_lock(struct drm_device *dev, return ret < 0 ? ret : 0; } -static void get_freesync_config_for_crtc( +STATIC_IFN_KUNIT void get_freesync_config_for_crtc( struct dm_crtc_state *new_crtc_state, struct dm_connector_state *new_con_state) { @@ -5778,8 +5778,9 @@ static void get_freesync_config_for_crtc( out: new_crtc_state->freesync_config = config; } +EXPORT_IF_KUNIT(get_freesync_config_for_crtc); -static void reset_freesync_config_for_crtc( +STATIC_IFN_KUNIT void reset_freesync_config_for_crtc( struct dm_crtc_state *new_crtc_state) { new_crtc_state->vrr_supported = false; @@ -5787,6 +5788,7 @@ static void reset_freesync_config_for_crtc( memset(&new_crtc_state->vrr_infopacket, 0, sizeof(new_crtc_state->vrr_infopacket)); } +EXPORT_IF_KUNIT(reset_freesync_config_for_crtc); STATIC_IFN_KUNIT bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 3a2fb5193668..7c410d766573 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1178,6 +1178,9 @@ bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, struct dm_crtc_state *new_state); void set_multisync_trigger_params(struct dc_stream_state *stream); void set_master_stream(struct dc_stream_state *stream_set[], int stream_count); +void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); +void get_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state, + struct dm_connector_state *new_con_state); struct hdcp_workqueue; bool is_content_protection_different(struct drm_crtc_state *new_crtc_state, struct drm_crtc_state *old_crtc_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 94be7fa28547..34f3c1bb9152 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -1650,6 +1650,194 @@ static void dm_test_cp_diff_desired_to_undesired(struct kunit *test) KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); } +/* Tests for get_freesync_config_for_crtc() */ + +struct dm_test_freesync_ctx { + struct amdgpu_dm_connector *aconnector; + struct dm_crtc_state *crtc_state; + struct dm_connector_state *conn_state; + struct dc_stream_state *stream; +}; + +static struct dm_test_freesync_ctx *dm_test_freesync_ctx_alloc(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->crtc_state = kunit_kzalloc(test, sizeof(*ctx->crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->crtc_state); + ctx->conn_state = kunit_kzalloc(test, sizeof(*ctx->conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state); + ctx->stream = dm_kunit_alloc_stream(test, NULL); + + ctx->conn_state->base.connector = &ctx->aconnector->base; + ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_DisplayPort; + ctx->crtc_state->stream = ctx->stream; + + /* 1080p60 timing so drm_mode_vrefresh() == 60 */ + ctx->crtc_state->base.mode.clock = 148500; + ctx->crtc_state->base.mode.htotal = 2200; + ctx->crtc_state->base.mode.vtotal = 1125; + + return ctx; +} + +/** + * dm_test_freesync_config_writeback - Test writeback connector is left untouched + * @test: The KUnit test context + */ +static void dm_test_freesync_config_writeback(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + ctx->crtc_state->vrr_supported = true; /* sentinel: must stay set */ + + get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + /* Writeback: early return leaves vrr_supported sentinel untouched */ + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); +} + +/** + * dm_test_freesync_config_not_capable - Test a non-freesync sink reports UNSUPPORTED + * @test: The KUnit test context + */ +static void dm_test_freesync_config_not_capable(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = false; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + + get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_UNSUPPORTED); +} + +/** + * dm_test_freesync_config_out_of_range - Test a refresh outside the range is UNSUPPORTED + * @test: The KUnit test context + */ +static void dm_test_freesync_config_out_of_range(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 90; /* 60 < 90 -> out of range */ + ctx->aconnector->max_vfreq = 120; + + get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_UNSUPPORTED); +} + +/** + * dm_test_freesync_config_active_variable - Test vrr_enabled yields ACTIVE_VARIABLE + * @test: The KUnit test context + */ +static void dm_test_freesync_config_active_variable(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + ctx->crtc_state->base.vrr_enabled = true; + + get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_TRUE(test, ctx->stream->ignore_msa_timing_param); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_ACTIVE_VARIABLE); + KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.min_refresh_in_uhz, + 48000000U); + KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.max_refresh_in_uhz, + 120000000U); + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.vsif_supported); + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.btr); +} + +/** + * dm_test_freesync_config_inactive - Test supported-but-off yields INACTIVE + * @test: The KUnit test context + */ +static void dm_test_freesync_config_inactive(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + ctx->crtc_state->base.vrr_enabled = false; + + get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_INACTIVE); +} + +/** + * dm_test_freesync_config_active_fixed - Test freesync-video mode yields ACTIVE_FIXED + * @test: The KUnit test context + */ +static void dm_test_freesync_config_active_fixed(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + /* Pre-set fixed state selects the freesync-video (fixed) path */ + ctx->crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; + ctx->crtc_state->freesync_config.fixed_refresh_in_uhz = 60000000; + ctx->crtc_state->base.vrr_enabled = true; /* ignored on the fixed path */ + + get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_ACTIVE_FIXED); + KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.fixed_refresh_in_uhz, + 60000000U); +} + +/* Tests for reset_freesync_config_for_crtc() */ + +/** + * dm_test_reset_freesync_config - Test reset clears vrr support and info packet + * @test: The KUnit test context + */ +static void dm_test_reset_freesync_config(struct kunit *test) +{ + struct dm_crtc_state *crtc_state; + + crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc_state); + + crtc_state->vrr_supported = true; + crtc_state->vrr_infopacket.valid = true; + + reset_freesync_config_for_crtc(crtc_state); + + KUNIT_EXPECT_FALSE(test, crtc_state->vrr_supported); + KUNIT_EXPECT_FALSE(test, crtc_state->vrr_infopacket.valid); +} + static struct kunit_case amdgpu_dm_tests[] = { /* Simple DM callbacks */ KUNIT_CASE(dm_test_is_idle), @@ -1742,6 +1930,15 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_cp_diff_s3_undesired_to_enabled), KUNIT_CASE(dm_test_cp_diff_desired_to_enabled), KUNIT_CASE(dm_test_cp_diff_desired_to_undesired), + /* get_freesync_config_for_crtc */ + KUNIT_CASE(dm_test_freesync_config_writeback), + KUNIT_CASE(dm_test_freesync_config_not_capable), + KUNIT_CASE(dm_test_freesync_config_out_of_range), + KUNIT_CASE(dm_test_freesync_config_active_variable), + KUNIT_CASE(dm_test_freesync_config_inactive), + KUNIT_CASE(dm_test_freesync_config_active_fixed), + /* reset_freesync_config_for_crtc */ + KUNIT_CASE(dm_test_reset_freesync_config), {} }; -- cgit v1.2.3 From b01e10bd9fc1c3e76582991bf1937a871f1b12ce Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 19:23:04 -0600 Subject: drm/amd/display: add KUnit tests for per-frame master sync [WHAT] Add KUnit tests for dm_enable_per_frame_crtc_master_sync covering the single-stream no-op, the two-stream master selection with trigger parameters, and the NULL-stream skip path. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 5 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 1 + .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 103 +++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index ec08aed843da..d6e0175d9668 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -141,7 +141,7 @@ static void amdgpu_dm_destroy_drm_device(struct amdgpu_display_manager *dm); static int amdgpu_dm_atomic_setup_commit(struct drm_atomic_commit *state); static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state); -static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context); +STATIC_IFN_KUNIT void dm_enable_per_frame_crtc_master_sync(struct dc_state *context); static int amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); @@ -5344,7 +5344,7 @@ STATIC_IFN_KUNIT void set_master_stream(struct dc_stream_state *stream_set[], } EXPORT_IF_KUNIT(set_master_stream); -static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) +STATIC_IFN_KUNIT void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) { int i = 0; struct dc_stream_state *stream; @@ -5372,6 +5372,7 @@ static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) set_multisync_trigger_params(stream); } } +EXPORT_IF_KUNIT(dm_enable_per_frame_crtc_master_sync); /** * amdgpu_dm_atomic_commit_tail() - AMDgpu DM's commit tail implementation. diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 7c410d766573..61d75f2a285d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1181,6 +1181,7 @@ void set_master_stream(struct dc_stream_state *stream_set[], int stream_count); void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); void get_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state, struct dm_connector_state *new_con_state); +void dm_enable_per_frame_crtc_master_sync(struct dc_state *context); struct hdcp_workqueue; bool is_content_protection_different(struct drm_crtc_state *new_crtc_state, struct drm_crtc_state *old_crtc_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 34f3c1bb9152..c0516ce2311b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -1838,6 +1838,105 @@ static void dm_test_reset_freesync_config(struct kunit *test) KUNIT_EXPECT_FALSE(test, crtc_state->vrr_infopacket.valid); } +/* Tests for dm_enable_per_frame_crtc_master_sync() */ + +/** + * dm_test_per_frame_master_sync_single_stream - Test fewer than two streams is a no-op + * @test: The KUnit test context + */ +static void dm_test_per_frame_master_sync_single_stream(struct kunit *test) +{ + struct dc_state *context; + struct dc_stream_state *stream; + + context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, context); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->triggered_crtc_reset.enabled = true; + context->streams[0] = stream; + context->stream_count = 1; + + dm_enable_per_frame_crtc_master_sync(context); + + /* < 2 streams: early return, event_source stays NULL */ + KUNIT_EXPECT_NULL(test, stream->triggered_crtc_reset.event_source); +} + +/** + * dm_test_per_frame_master_sync_two_streams - Test the master is picked and applied + * @test: The KUnit test context + */ +static void dm_test_per_frame_master_sync_two_streams(struct kunit *test) +{ + struct dc_state *context; + struct dc_stream_state *stream0, *stream1; + + context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, context); + stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream0); + stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream1); + + /* stream0 60Hz, stream1 120Hz, both trigger-reset enabled */ + stream0->triggered_crtc_reset.enabled = true; + stream0->timing.pix_clk_100hz = 1485000; + stream0->timing.h_total = 2200; + stream0->timing.v_total = 1125; + stream1->triggered_crtc_reset.enabled = true; + stream1->timing.pix_clk_100hz = 2970000; + stream1->timing.h_total = 2200; + stream1->timing.v_total = 1125; + stream1->timing.flags.VSYNC_POSITIVE_POLARITY = 1; + + context->streams[0] = stream0; + context->streams[1] = stream1; + context->stream_count = 2; + + dm_enable_per_frame_crtc_master_sync(context); + + /* set_master_stream picks the highest refresh (stream1) as event source */ + KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source, + stream1); + KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source, + stream1); + /* set_multisync_trigger_params applied to enabled streams */ + KUNIT_EXPECT_EQ(test, (int)stream0->triggered_crtc_reset.event, + (int)CRTC_EVENT_VSYNC_RISING); + KUNIT_EXPECT_EQ(test, (int)stream0->triggered_crtc_reset.delay, + (int)TRIGGER_DELAY_NEXT_PIXEL); +} + +/** + * dm_test_per_frame_master_sync_skips_null_stream - Test NULL stream entries are skipped + * @test: The KUnit test context + */ +static void dm_test_per_frame_master_sync_skips_null_stream(struct kunit *test) +{ + struct dc_state *context; + struct dc_stream_state *stream; + + context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, context); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->triggered_crtc_reset.enabled = true; + stream->timing.pix_clk_100hz = 1485000; + stream->timing.h_total = 2200; + stream->timing.v_total = 1125; + context->streams[0] = stream; + context->streams[1] = NULL; + context->stream_count = 2; + + dm_enable_per_frame_crtc_master_sync(context); + + KUNIT_EXPECT_PTR_EQ(test, stream->triggered_crtc_reset.event_source, + stream); +} + static struct kunit_case amdgpu_dm_tests[] = { /* Simple DM callbacks */ KUNIT_CASE(dm_test_is_idle), @@ -1939,6 +2038,10 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_freesync_config_active_fixed), /* reset_freesync_config_for_crtc */ KUNIT_CASE(dm_test_reset_freesync_config), + /* dm_enable_per_frame_crtc_master_sync */ + KUNIT_CASE(dm_test_per_frame_master_sync_single_stream), + KUNIT_CASE(dm_test_per_frame_master_sync_two_streams), + KUNIT_CASE(dm_test_per_frame_master_sync_skips_null_stream), {} }; -- cgit v1.2.3 From 753f41b30f6fd5bd77f890f7d066d825531435c9 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 19:23:04 -0600 Subject: drm/amd/display: add KUnit tests for stutter quirk [WHAT] Add KUnit tests for dm_should_disable_stutter covering a full quirk match, a non-matching device, and a partial match that differs only in the PCI revision. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 3 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 2 + .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 64 ++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index d6e0175d9668..47f131668bea 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -424,7 +424,7 @@ static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = { { 0, 0, 0, 0, 0 }, }; -static bool dm_should_disable_stutter(struct pci_dev *pdev) +STATIC_IFN_KUNIT bool dm_should_disable_stutter(struct pci_dev *pdev) { const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list; @@ -440,6 +440,7 @@ static bool dm_should_disable_stutter(struct pci_dev *pdev) } return false; } +EXPORT_IF_KUNIT(dm_should_disable_stutter); void* diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 61d75f2a285d..03189f44efd4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1178,6 +1178,8 @@ bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, struct dm_crtc_state *new_state); void set_multisync_trigger_params(struct dc_stream_state *stream); void set_master_stream(struct dc_stream_state *stream_set[], int stream_count); +struct pci_dev; +bool dm_should_disable_stutter(struct pci_dev *pdev); void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); void get_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state, struct dm_connector_state *new_con_state); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index c0516ce2311b..a0b4d581a546 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -1937,6 +1937,66 @@ static void dm_test_per_frame_master_sync_skips_null_stream(struct kunit *test) stream); } +/* Tests for dm_should_disable_stutter() */ + +/** + * dm_test_should_disable_stutter_match - Test the quirk device matches + * @test: The KUnit test context + */ +static void dm_test_should_disable_stutter_match(struct kunit *test) +{ + struct pci_dev *pdev; + + pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pdev); + + pdev->vendor = 0x1002; + pdev->device = 0x15dd; + pdev->subsystem_vendor = 0x1002; + pdev->subsystem_device = 0x15dd; + pdev->revision = 0xc8; + + KUNIT_EXPECT_TRUE(test, dm_should_disable_stutter(pdev)); +} + +/** + * dm_test_should_disable_stutter_no_match - Test a non-quirk device does not match + * @test: The KUnit test context + */ +static void dm_test_should_disable_stutter_no_match(struct kunit *test) +{ + struct pci_dev *pdev; + + pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pdev); + + pdev->vendor = 0x1002; + pdev->device = 0x1234; + + KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev)); +} + +/** + * dm_test_should_disable_stutter_revision_differs - Test a partial match (revision) fails + * @test: The KUnit test context + */ +static void dm_test_should_disable_stutter_revision_differs(struct kunit *test) +{ + struct pci_dev *pdev; + + pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pdev); + + /* Everything matches the quirk except the revision */ + pdev->vendor = 0x1002; + pdev->device = 0x15dd; + pdev->subsystem_vendor = 0x1002; + pdev->subsystem_device = 0x15dd; + pdev->revision = 0x00; + + KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev)); +} + static struct kunit_case amdgpu_dm_tests[] = { /* Simple DM callbacks */ KUNIT_CASE(dm_test_is_idle), @@ -2042,6 +2102,10 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_per_frame_master_sync_single_stream), KUNIT_CASE(dm_test_per_frame_master_sync_two_streams), KUNIT_CASE(dm_test_per_frame_master_sync_skips_null_stream), + /* dm_should_disable_stutter */ + KUNIT_CASE(dm_test_should_disable_stutter_match), + KUNIT_CASE(dm_test_should_disable_stutter_no_match), + KUNIT_CASE(dm_test_should_disable_stutter_revision_differs), {} }; -- cgit v1.2.3 From d79f3c7fe158e0e7460e09c33eae729f42c5bf37 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 19:23:04 -0600 Subject: drm/amd/display: add KUnit tests for DPCD poweroff delay [WHAT] Add KUnit tests for amdgpu_dm_apply_delay_after_dpcd_poweroff covering the NULL-sink early return, the zero-wait skip path, and the non-zero wait interval. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 + .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 48 ++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 47f131668bea..4b52ed3fb2bf 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1918,6 +1918,7 @@ void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, ppatch->wait_after_dpcd_poweroff_ms / 1000); } } +EXPORT_IF_KUNIT(amdgpu_dm_apply_delay_after_dpcd_poweroff); /** * amdgpu_dm_dump_links_and_sinks - Debug dump of all DC links and their sinks diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index a0b4d581a546..f967eced6dbb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -1997,6 +1997,50 @@ static void dm_test_should_disable_stutter_revision_differs(struct kunit *test) KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev)); } +/* Tests for amdgpu_dm_apply_delay_after_dpcd_poweroff() */ + +/** + * dm_test_apply_delay_null_sink - Test a NULL sink returns without delay + * @test: The KUnit test context + */ +static void dm_test_apply_delay_null_sink(struct kunit *test) +{ + /* NULL sink: early return, no delay, no dereference */ + amdgpu_dm_apply_delay_after_dpcd_poweroff(NULL, NULL); +} + +/** + * dm_test_apply_delay_zero_wait - Test a zero wait interval skips the delay + * @test: The KUnit test context + */ +static void dm_test_apply_delay_zero_wait(struct kunit *test) +{ + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + /* wait == 0: no msleep, adev is unused so NULL is safe */ + sink->edid_caps.panel_patch.wait_after_dpcd_poweroff_ms = 0; + amdgpu_dm_apply_delay_after_dpcd_poweroff(NULL, sink); +} + +/** + * dm_test_apply_delay_nonzero_wait - Test a non-zero wait interval executes delay path + * @test: The KUnit test context + */ +static void dm_test_apply_delay_nonzero_wait(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->edid_caps.panel_patch.wait_after_dpcd_poweroff_ms = 1; + amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, sink); +} + static struct kunit_case amdgpu_dm_tests[] = { /* Simple DM callbacks */ KUNIT_CASE(dm_test_is_idle), @@ -2106,6 +2150,10 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_should_disable_stutter_match), KUNIT_CASE(dm_test_should_disable_stutter_no_match), KUNIT_CASE(dm_test_should_disable_stutter_revision_differs), + /* amdgpu_dm_apply_delay_after_dpcd_poweroff */ + KUNIT_CASE(dm_test_apply_delay_null_sink), + KUNIT_CASE(dm_test_apply_delay_zero_wait), + KUNIT_CASE(dm_test_apply_delay_nonzero_wait), {} }; -- cgit v1.2.3 From e3a1ebae264959212d36df0487e47843a802ca54 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 20:54:22 -0600 Subject: drm/amd/display: add CRC source list KUnit coverage [WHAT] Expose amdgpu_dm_crtc_get_crc_sources() for KUnit and add a test that verifies the returned static source-name list and its count. This is the accessor used by the debugfs CRC control interface. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_crc_test.c | 26 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index bd7444b05d6d..282b8ff0eaad 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -152,6 +152,7 @@ const char *const *amdgpu_dm_crtc_get_crc_sources(struct drm_crtc *crtc, *count = ARRAY_SIZE(pipe_crc_sources); return pipe_crc_sources; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_get_crc_sources); #ifdef CONFIG_DRM_AMD_SECURE_DISPLAY static void update_phy_id_mapping(struct amdgpu_device *adev) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c index a6fd3a6fd803..88f7a15853e8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c @@ -95,6 +95,30 @@ static void dm_test_is_valid_crc_source(struct kunit *test) KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); } +/** + * dm_test_crtc_get_crc_sources() - Test available CRC source strings. + * @test: KUnit test context. + * + * Verifies that amdgpu_dm_crtc_get_crc_sources() returns the static source + * list and reports all debugfs source names. + */ +static void dm_test_crtc_get_crc_sources(struct kunit *test) +{ + const char *const *sources; + size_t count = 0; + + sources = amdgpu_dm_crtc_get_crc_sources(NULL, &count); + + KUNIT_ASSERT_NOT_NULL(test, sources); + KUNIT_EXPECT_EQ(test, count, 6); + KUNIT_EXPECT_STREQ(test, sources[0], "none"); + KUNIT_EXPECT_STREQ(test, sources[1], "crtc"); + KUNIT_EXPECT_STREQ(test, sources[2], "crtc dither"); + KUNIT_EXPECT_STREQ(test, sources[3], "dprx"); + KUNIT_EXPECT_STREQ(test, sources[4], "dprx dither"); + KUNIT_EXPECT_STREQ(test, sources[5], "auto"); +} + /** * dm_test_need_dp_aux() - Test dm_need_dp_aux(). * @test: KUnit test context. @@ -222,6 +246,8 @@ static struct kunit_case dm_crc_test_cases[] = { KUNIT_CASE(dm_test_need_crc_dither), /* amdgpu_dm_is_valid_crc_source() */ KUNIT_CASE(dm_test_is_valid_crc_source), + /* amdgpu_dm_crtc_get_crc_sources() */ + KUNIT_CASE(dm_test_crtc_get_crc_sources), /* dm_need_dp_aux() */ KUNIT_CASE(dm_test_need_dp_aux), /* dm_crc_source_should_start_dprx() */ -- cgit v1.2.3 From e714db7ff32d16ac003ef70f2165790edf227854 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 20:56:53 -0600 Subject: drm/amd/display: add CRC source verify KUnit coverage [WHAT] Expose amdgpu_dm_crtc_verify_crc_source() for KUnit and add tests for the valid and invalid source-name paths. Introduce the shared CRTC test scaffolding (dm_test_alloc_crc_crtc() plus the DM device test includes) used by the remaining CRTC CRC tests. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_crc_test.c | 66 ++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index 282b8ff0eaad..8cbb0384825d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -565,6 +565,7 @@ amdgpu_dm_crtc_verify_crc_source(struct drm_crtc *crtc, const char *src_name, *values_cnt = 3; return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_verify_crc_source); int amdgpu_dm_crtc_configure_crc_source(struct drm_crtc *crtc, struct dm_crtc_state *dm_crtc_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c index 88f7a15853e8..4fa0bd9669c4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c @@ -7,7 +7,30 @@ #include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_crc.h" +#include "amdgpu_dm_kunit_test_helpers.h" + +static struct amdgpu_crtc *dm_test_alloc_crc_crtc(struct kunit *test, + struct amdgpu_device *adev) +{ + struct amdgpu_crtc *acrtc; + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + acrtc->base.dev = &adev->ddev; + drm_modeset_lock_init(&acrtc->base.mutex); + spin_lock_init(&acrtc->base.commit_lock); + INIT_LIST_HEAD(&acrtc->base.commit_list); + + return acrtc; +} static void dm_test_parse_crc_source_none(struct kunit *test) { @@ -119,6 +142,46 @@ static void dm_test_crtc_get_crc_sources(struct kunit *test) KUNIT_EXPECT_STREQ(test, sources[5], "auto"); } +/** + * dm_test_crtc_verify_crc_source_valid() - Test valid CRC source verification. + * @test: KUnit test context. + * + * Verifies that valid source strings return success and request three CRC + * values. + */ +static void dm_test_crtc_verify_crc_source_valid(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + size_t values_cnt = 0; + int ret; + + ret = amdgpu_dm_crtc_verify_crc_source(&acrtc->base, "crtc", &values_cnt); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, values_cnt, 3); +} + +/** + * dm_test_crtc_verify_crc_source_invalid() - Test invalid CRC source verification. + * @test: KUnit test context. + * + * Verifies that invalid source strings are rejected without changing the + * caller-provided values count. + */ +static void dm_test_crtc_verify_crc_source_invalid(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + size_t values_cnt = 7; + int ret; + + ret = amdgpu_dm_crtc_verify_crc_source(&acrtc->base, "bad", &values_cnt); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, values_cnt, 7); +} + /** * dm_test_need_dp_aux() - Test dm_need_dp_aux(). * @test: KUnit test context. @@ -248,6 +311,9 @@ static struct kunit_case dm_crc_test_cases[] = { KUNIT_CASE(dm_test_is_valid_crc_source), /* amdgpu_dm_crtc_get_crc_sources() */ KUNIT_CASE(dm_test_crtc_get_crc_sources), + /* amdgpu_dm_crtc_verify_crc_source() */ + KUNIT_CASE(dm_test_crtc_verify_crc_source_valid), + KUNIT_CASE(dm_test_crtc_verify_crc_source_invalid), /* dm_need_dp_aux() */ KUNIT_CASE(dm_test_need_dp_aux), /* dm_crc_source_should_start_dprx() */ -- cgit v1.2.3 From e03f5358ad1b0be96961b7da1b1b3d61b0d50824 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 21:03:00 -0600 Subject: drm/amd/display: add CRC configure KUnit coverage [WHAT] Expose amdgpu_dm_crtc_configure_crc_source() for KUnit and add tests for the CRTC-enable, disable (NONE), DPRX, DPRX-dither, DCN3.6 polynomial select, and DC-configure-failure paths. Introduce a small fake DC fixture (timing-generator and OPP callbacks over an empty resource context) so the configure path can be exercised without real hardware. The fixture is shared with the CRC IRQ tests. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_crc_test.c | 343 +++++++++++++++++++++ 2 files changed, 344 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index 8cbb0384825d..208c255f0dc7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -638,6 +638,7 @@ unlock: return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_configure_crc_source); int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c index 4fa0bd9669c4..383646ad7005 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c @@ -10,12 +10,150 @@ #include #include "dc.h" +#include "core_types.h" +#include "logger_types.h" +#include "opp.h" +#include "timing_generator.h" #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_crc.h" #include "amdgpu_dm_kunit_test_helpers.h" +struct dm_test_crc_dc_fixture { + struct dc *dc; + struct dc_context *dc_ctx; + struct dc_state *dc_state; + struct dc_stream_state *stream; + struct dc_link *link; + struct timing_generator *tg; + struct output_pixel_processor *opp; + struct dal_logger *logger; + struct dm_crtc_state *dm_state; + struct crc_params crc_params; + enum dc_dynamic_expansion dyn_expansion; + enum dc_dither_option dither_option; + uint32_t crc_r; + uint32_t crc_g; + uint32_t crc_b; + bool configure_crc_called; + bool dyn_expansion_called; + bool bit_depth_reduction_called; + bool configure_crc_return; + bool get_crc_called; + bool get_crc_return; +}; + +static struct dm_test_crc_dc_fixture *dm_test_crc_dc_ctx; + +static bool dm_test_configure_crc(struct timing_generator *tg, + const struct crc_params *params) +{ + if (!dm_test_crc_dc_ctx) + return false; + + dm_test_crc_dc_ctx->configure_crc_called = true; + dm_test_crc_dc_ctx->crc_params = *params; + + return dm_test_crc_dc_ctx->configure_crc_return; +} + +static bool dm_test_get_crc(struct timing_generator *tg, uint8_t idx, + uint32_t *r_cr, uint32_t *g_y, uint32_t *b_cb) +{ + if (!dm_test_crc_dc_ctx) + return false; + + dm_test_crc_dc_ctx->get_crc_called = true; + *r_cr = dm_test_crc_dc_ctx->crc_r; + *g_y = dm_test_crc_dc_ctx->crc_g; + *b_cb = dm_test_crc_dc_ctx->crc_b; + + return dm_test_crc_dc_ctx->get_crc_return; +} + +static void dm_test_opp_set_dyn_expansion(struct output_pixel_processor *opp, + enum dc_color_space color_sp, + enum dc_color_depth color_dpth, + enum signal_type signal) +{ + if (!dm_test_crc_dc_ctx) + return; + + dm_test_crc_dc_ctx->dyn_expansion_called = true; + dm_test_crc_dc_ctx->dyn_expansion = opp->dyn_expansion; +} + +static void dm_test_opp_program_bit_depth_reduction(struct output_pixel_processor *opp, + const struct bit_depth_reduction_params *params) +{ + if (!dm_test_crc_dc_ctx) + return; + + dm_test_crc_dc_ctx->bit_depth_reduction_called = true; +} + +static const struct timing_generator_funcs dm_test_tg_funcs = { + .configure_crc = dm_test_configure_crc, + .get_crc = dm_test_get_crc, +}; + +static const struct opp_funcs dm_test_opp_funcs = { + .opp_set_dyn_expansion = dm_test_opp_set_dyn_expansion, + .opp_program_bit_depth_reduction = dm_test_opp_program_bit_depth_reduction, +}; + +static struct dm_test_crc_dc_fixture *dm_test_alloc_crc_dc_fixture(struct kunit *test, + struct amdgpu_device *adev) +{ + struct dm_test_crc_dc_fixture *fixture; + struct pipe_ctx *pipe; + + fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture); + + fixture->dm_state = kunit_kzalloc(test, sizeof(*fixture->dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->dm_state); + fixture->dc = kunit_kzalloc(test, sizeof(*fixture->dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->dc); + fixture->dc_ctx = kunit_kzalloc(test, sizeof(*fixture->dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->dc_ctx); + fixture->dc_state = kunit_kzalloc(test, sizeof(*fixture->dc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->dc_state); + fixture->tg = kunit_kzalloc(test, sizeof(*fixture->tg), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->tg); + fixture->opp = kunit_kzalloc(test, sizeof(*fixture->opp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->opp); + fixture->logger = kunit_kzalloc(test, sizeof(*fixture->logger), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture->logger); + fixture->link = dm_kunit_alloc_link(test); + fixture->stream = dm_kunit_alloc_stream(test, fixture->link); + + mutex_init(&adev->dm.dc_lock); + adev->dm.dc = fixture->dc; + fixture->dc->ctx = fixture->dc_ctx; + fixture->dc->current_state = fixture->dc_state; + fixture->dc_ctx->dc = fixture->dc; + fixture->dc_ctx->logger = fixture->logger; + fixture->link->dc = fixture->dc; + fixture->stream->ctx = fixture->dc_ctx; + fixture->stream->link = fixture->link; + fixture->stream->timing.h_addressable = 1920; + fixture->stream->timing.v_addressable = 1080; + fixture->configure_crc_return = true; + fixture->tg->funcs = &dm_test_tg_funcs; + fixture->opp->funcs = &dm_test_opp_funcs; + fixture->dm_state->stream = fixture->stream; + + pipe = &fixture->dc_state->res_ctx.pipe_ctx[0]; + pipe->stream = fixture->stream; + pipe->pipe_idx = 0; + pipe->stream_res.tg = fixture->tg; + pipe->stream_res.opp = fixture->opp; + + return fixture; +} + static struct amdgpu_crtc *dm_test_alloc_crc_crtc(struct kunit *test, struct amdgpu_device *adev) { @@ -182,6 +320,203 @@ static void dm_test_crtc_verify_crc_source_invalid(struct kunit *test) KUNIT_EXPECT_EQ(test, values_cnt, 7); } +/** + * dm_test_crtc_configure_crc_source_no_stream() - Test missing stream handling. + * @test: KUnit test context. + * + * Verifies that configuration is deferred/rejected before any DC access when + * the CRTC state does not have a stream. + */ +static void dm_test_crtc_configure_crc_source_no_stream(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + int ret; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_crtc_configure_crc_source_dprx() - Test DPRX configure path. + * @test: KUnit test context. + * + * Verifies that a DPRX source can be configured with an empty DC resource + * state, covering the non-CRTC path that only updates dither/dynamic expansion. + */ +static void dm_test_crtc_configure_crc_source_dprx(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_FALSE(test, fixture->configure_crc_called); + KUNIT_EXPECT_TRUE(test, fixture->dyn_expansion_called); + KUNIT_EXPECT_EQ(test, fixture->dyn_expansion, DYN_EXPANSION_DISABLE); + KUNIT_EXPECT_TRUE(test, fixture->bit_depth_reduction_called); +} + +/** + * dm_test_crtc_configure_crc_source_dprx_dither() - Test DPRX dither path. + * @test: KUnit test context. + * + * Verifies that a DPRX dither source reaches the default dither/dynamic + * expansion path without requiring timing-generator callbacks. + */ +static void dm_test_crtc_configure_crc_source_dprx_dither(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_FALSE(test, fixture->configure_crc_called); + KUNIT_EXPECT_TRUE(test, fixture->dyn_expansion_called); + KUNIT_EXPECT_EQ(test, fixture->dyn_expansion, DYN_EXPANSION_AUTO); + KUNIT_EXPECT_TRUE(test, fixture->bit_depth_reduction_called); +} + +/** + * dm_test_crtc_configure_crc_source_crtc() - Test CRTC enable path. + * @test: KUnit test context. + * + * Verifies that a CRTC source enables DC CRC capture and disables dither. + */ +static void dm_test_crtc_configure_crc_source_crtc(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, fixture->configure_crc_called); + KUNIT_EXPECT_TRUE(test, fixture->crc_params.enable); + KUNIT_EXPECT_TRUE(test, fixture->crc_params.continuous_mode); + KUNIT_EXPECT_TRUE(test, fixture->crc_params.reset); + KUNIT_EXPECT_EQ(test, fixture->crc_params.windowa_x_end, 1920); + KUNIT_EXPECT_EQ(test, fixture->crc_params.windowa_y_end, 1080); + KUNIT_EXPECT_TRUE(test, fixture->dyn_expansion_called); + KUNIT_EXPECT_EQ(test, fixture->dyn_expansion, DYN_EXPANSION_DISABLE); + KUNIT_EXPECT_TRUE(test, fixture->bit_depth_reduction_called); +} + +/** + * dm_test_crtc_configure_crc_source_crtc_dcn36_poly() - Test CRC poly select. + * @test: KUnit test context. + * + * Verifies that DCN3.6+ configurations use the CRTC-selected CRC polynomial. + */ +static void dm_test_crtc_configure_crc_source_crtc_dcn36_poly(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 6, 0); + acrtc->dm_irq_params.crc_poly_mode = CRC_POLY_MODE_32; + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, fixture->configure_crc_called); + KUNIT_EXPECT_EQ(test, fixture->crc_params.crc_poly_mode, CRC_POLY_MODE_32); +} + +/** + * dm_test_crtc_configure_crc_source_crtc_configure_fails() - Test failure path. + * @test: KUnit test context. + * + * Verifies that a DC CRC configuration failure is reported as -EINVAL and + * stops before dither/dynamic expansion programming. + */ +static void dm_test_crtc_configure_crc_source_crtc_configure_fails(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + fixture->configure_crc_return = false; + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_TRUE(test, fixture->configure_crc_called); + KUNIT_EXPECT_FALSE(test, fixture->dyn_expansion_called); + KUNIT_EXPECT_FALSE(test, fixture->bit_depth_reduction_called); +} + +/** + * dm_test_crtc_configure_crc_source_none() - Test CRC disable path. + * @test: KUnit test context. + * + * Verifies that source NONE disables DC CRC capture and restores default + * dither/dynamic expansion. + */ +static void dm_test_crtc_configure_crc_source_none(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + int ret; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + dm_test_crc_dc_ctx = fixture; + + ret = amdgpu_dm_crtc_configure_crc_source(&acrtc->base, fixture->dm_state, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, fixture->configure_crc_called); + KUNIT_EXPECT_FALSE(test, fixture->crc_params.enable); + KUNIT_EXPECT_FALSE(test, fixture->crc_params.continuous_mode); + KUNIT_EXPECT_TRUE(test, fixture->crc_params.reset); + KUNIT_EXPECT_TRUE(test, fixture->dyn_expansion_called); + KUNIT_EXPECT_EQ(test, fixture->dyn_expansion, DYN_EXPANSION_AUTO); + KUNIT_EXPECT_TRUE(test, fixture->bit_depth_reduction_called); +} + /** * dm_test_need_dp_aux() - Test dm_need_dp_aux(). * @test: KUnit test context. @@ -314,6 +649,14 @@ static struct kunit_case dm_crc_test_cases[] = { /* amdgpu_dm_crtc_verify_crc_source() */ KUNIT_CASE(dm_test_crtc_verify_crc_source_valid), KUNIT_CASE(dm_test_crtc_verify_crc_source_invalid), + /* amdgpu_dm_crtc_configure_crc_source() */ + KUNIT_CASE(dm_test_crtc_configure_crc_source_no_stream), + KUNIT_CASE(dm_test_crtc_configure_crc_source_dprx), + KUNIT_CASE(dm_test_crtc_configure_crc_source_dprx_dither), + KUNIT_CASE(dm_test_crtc_configure_crc_source_crtc), + KUNIT_CASE(dm_test_crtc_configure_crc_source_crtc_dcn36_poly), + KUNIT_CASE(dm_test_crtc_configure_crc_source_crtc_configure_fails), + KUNIT_CASE(dm_test_crtc_configure_crc_source_none), /* dm_need_dp_aux() */ KUNIT_CASE(dm_test_need_dp_aux), /* dm_crc_source_should_start_dprx() */ -- cgit v1.2.3 From f22a403ff53bd80b17eea29275b904c48c9affa7 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 21:05:28 -0600 Subject: drm/amd/display: add CRC set-source KUnit coverage [WHAT] Expose amdgpu_dm_crtc_set_crc_source() for KUnit and add tests for the invalid-source guard, the valid NONE no-stream exit, the pending-commit wait/put path, and the DPRX connector-walk that returns -EINVAL when no matching DP connector is attached. Add connector test helpers (drm_connector_funcs plus a cleanup action) to back the DPRX connector-walk test. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_crc_test.c | 161 +++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index 208c255f0dc7..f72b99968213 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -816,6 +816,7 @@ cleanup: return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_set_crc_source); /** * amdgpu_dm_crtc_handle_crc_irq: Report to DRM the CRC on given CRTC. diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c index 383646ad7005..96bdb83317ec 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c @@ -7,7 +7,11 @@ #include +#include +#include +#include #include +#include #include "dc.h" #include "core_types.h" @@ -170,6 +174,19 @@ static struct amdgpu_crtc *dm_test_alloc_crc_crtc(struct kunit *test, return acrtc; } +static const struct drm_connector_funcs dm_test_crc_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, +}; + +static void dm_test_crc_connector_cleanup(void *data) +{ + drm_connector_cleanup(data); +} + static void dm_test_parse_crc_source_none(struct kunit *test) { KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_NONE, dm_parse_crc_source("none")); @@ -517,6 +534,145 @@ static void dm_test_crtc_configure_crc_source_none(struct kunit *test) KUNIT_EXPECT_TRUE(test, fixture->bit_depth_reduction_called); } +/** + * dm_test_crtc_set_crc_source_invalid() - Test invalid source guard. + * @test: KUnit test context. + * + * Verifies that amdgpu_dm_crtc_set_crc_source() rejects invalid source names + * before taking modeset locks, vblank references, or touching DC state. + */ +static void dm_test_crtc_set_crc_source_invalid(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + int ret; + + ret = amdgpu_dm_crtc_set_crc_source(&acrtc->base, "invalid"); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_crtc_set_crc_source_none_no_stream() - Test valid source no-stream exit. + * @test: KUnit test context. + * + * Verifies that a valid NONE request enters the set-source body, reads the + * current CRC state, and exits cleanly when configuration is deferred because + * no stream is attached. + */ +static void dm_test_crtc_set_crc_source_none_no_stream(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + int ret; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + acrtc->base.state = &dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_NONE; + + ret = amdgpu_dm_crtc_set_crc_source(&acrtc->base, "none"); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, acrtc->dm_irq_params.crc_src, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE); + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 0); +} + +/** + * dm_test_crtc_set_crc_source_none_commit() - Test set-source with pending commit. + * @test: KUnit test context. + * + * Verifies that a pending CRTC commit is acquired and waited on (already + * completed here so the wait returns immediately), then released during + * cleanup. Configuration is still deferred because no stream is attached. + */ +static void dm_test_crtc_set_crc_source_none_commit(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + struct drm_crtc_commit *commit; + int ret; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + acrtc->base.state = &dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_NONE; + + commit = kunit_kzalloc(test, sizeof(*commit), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, commit); + kref_init(&commit->ref); + init_completion(&commit->hw_done); + /* Mark the commit finished so the wait returns immediately. */ + complete_all(&commit->hw_done); + list_add_tail(&commit->commit_entry, &acrtc->base.commit_list); + + ret = amdgpu_dm_crtc_set_crc_source(&acrtc->base, "none"); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 0); +} + +/** + * dm_test_crtc_set_crc_source_dprx_no_connector() - Test DPRX with no match. + * @test: KUnit test context. + * + * Verifies that requesting a DPRX source walks the connector list and returns + * -EINVAL when no matching DP connector is attached to the CRTC. A stateless + * connector and a writeback connector exercise both connector filter branches. + */ +static void dm_test_crtc_set_crc_source_dprx_no_connector(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct drm_connector *dp_conn; + struct drm_connector *wb_conn; + struct dm_crtc_state *dm_state; + int ret; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + acrtc->base.state = &dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_NONE; + + /* Stateless DP connector: skipped by the !state filter. */ + dp_conn = kunit_kzalloc(test, sizeof(*dp_conn), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dp_conn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, dp_conn, + &dm_test_crc_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_crc_connector_cleanup, dp_conn), 0); + + /* Writeback connector bound to this CRTC: skipped by the WB filter. */ + wb_conn = kunit_kzalloc(test, sizeof(*wb_conn), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, wb_conn); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wb_conn, + &dm_test_crc_connector_funcs, + DRM_MODE_CONNECTOR_WRITEBACK), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_crc_connector_cleanup, wb_conn), 0); + drm_atomic_helper_connector_reset(wb_conn); + KUNIT_ASSERT_NOT_NULL(test, wb_conn->state); + wb_conn->state->crtc = &acrtc->base; + /* + * __drm_atomic_helper_connector_destroy_state() drops a connector + * reference when state->crtc is set. Balance it here since the CRTC is + * assigned directly rather than via drm_atomic_set_crtc_for_connector(), + * otherwise cleanup would drop the connector to zero and schedule an + * async free on the system workqueue. + */ + drm_connector_get(wb_conn); + + ret = amdgpu_dm_crtc_set_crc_source(&acrtc->base, "dprx"); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, acrtc->dm_irq_params.crc_src, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE); +} + /** * dm_test_need_dp_aux() - Test dm_need_dp_aux(). * @test: KUnit test context. @@ -657,6 +813,11 @@ static struct kunit_case dm_crc_test_cases[] = { KUNIT_CASE(dm_test_crtc_configure_crc_source_crtc_dcn36_poly), KUNIT_CASE(dm_test_crtc_configure_crc_source_crtc_configure_fails), KUNIT_CASE(dm_test_crtc_configure_crc_source_none), + /* amdgpu_dm_crtc_set_crc_source() */ + KUNIT_CASE(dm_test_crtc_set_crc_source_invalid), + KUNIT_CASE(dm_test_crtc_set_crc_source_none_no_stream), + KUNIT_CASE(dm_test_crtc_set_crc_source_none_commit), + KUNIT_CASE(dm_test_crtc_set_crc_source_dprx_no_connector), /* dm_need_dp_aux() */ KUNIT_CASE(dm_test_need_dp_aux), /* dm_crc_source_should_start_dprx() */ -- cgit v1.2.3 From 1cc234930e85488a80bdc41096c4daeaf8fe2280 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 6 Jul 2026 21:07:21 -0600 Subject: drm/amd/display: add CRC IRQ handler KUnit coverage [WHAT] Expose amdgpu_dm_crtc_handle_crc_irq() for KUnit and add tests for the incomplete-CRTC early returns, the disabled-source exit, the initial two-frame skip window, the DPRX post-skip exit, and the failed dc_stream_get_crc() path that stops before delivering a DRM CRC entry. These tests reuse the fake DC fixture introduced with the CRC configure coverage. Assisted-by: Copilot:Claude-Opus-4.8 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_crc_test.c | 132 +++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index f72b99968213..05387bedf24a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -874,6 +874,7 @@ void amdgpu_dm_crtc_handle_crc_irq(struct drm_crtc *crtc) drm_crtc_accurate_vblank_count(crtc), crcs); } } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_handle_crc_irq); #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) void amdgpu_dm_crtc_handle_crc_window_irq(struct drm_crtc *crtc) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c index 96bdb83317ec..29a513dab9cd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c @@ -673,6 +673,132 @@ static void dm_test_crtc_set_crc_source_dprx_no_connector(struct kunit *test) AMDGPU_DM_PIPE_CRC_SOURCE_NONE); } +/** + * dm_test_crtc_handle_crc_irq_early_returns() - Test null/missing state exits. + * @test: KUnit test context. + * + * Verifies that the CRC IRQ handler safely ignores incomplete CRTC objects. + */ +static void dm_test_crtc_handle_crc_irq_early_returns(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + + amdgpu_dm_crtc_handle_crc_irq(NULL); + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + acrtc->base.state = &dm_state->base; + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 0); +} + +/** + * dm_test_crtc_handle_crc_irq_disabled_source() - Test disabled source exit. + * @test: KUnit test context. + * + * Verifies that a present stream does not advance the skip counter when CRC + * capture is disabled. + */ +static void dm_test_crtc_handle_crc_irq_disabled_source(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + dm_state->stream = dm_kunit_alloc_stream(test, NULL); + acrtc->base.state = &dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_NONE; + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 0); +} + +/** + * dm_test_crtc_handle_crc_irq_skips_initial_frames() - Test initial skip logic. + * @test: KUnit test context. + * + * Verifies that the first two enabled CRC IRQs only increment crc_skip_count, + * avoiding the later DC CRC read path. + */ +static void dm_test_crtc_handle_crc_irq_skips_initial_frames(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_crtc_state *dm_state; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + dm_state->stream = dm_kunit_alloc_stream(test, NULL); + acrtc->base.state = &dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_CRTC; + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 1); + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + KUNIT_EXPECT_EQ(test, dm_state->crc_skip_count, 2); +} + +/** + * dm_test_crtc_handle_crc_irq_dprx_after_skip() - Test DPRX post-skip exit. + * @test: KUnit test context. + * + * Verifies that enabled non-CRTC CRC sources do not call into DC CRC reads + * after the initial skip window. + */ +static void dm_test_crtc_handle_crc_irq_dprx_after_skip(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + fixture->dm_state->crc_skip_count = 2; + acrtc->base.state = &fixture->dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_DPRX; + dm_test_crc_dc_ctx = fixture; + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_FALSE(test, fixture->get_crc_called); + KUNIT_EXPECT_EQ(test, fixture->dm_state->crc_skip_count, 2); +} + +/** + * dm_test_crtc_handle_crc_irq_get_crc_fails() - Test failed DC CRC read. + * @test: KUnit test context. + * + * Verifies that the IRQ handler exits after dc_stream_get_crc() returns false, + * before attempting to deliver a DRM CRC entry. + */ +static void dm_test_crtc_handle_crc_irq_get_crc_fails(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_crtc *acrtc = dm_test_alloc_crc_crtc(test, adev); + struct dm_test_crc_dc_fixture *fixture; + + fixture = dm_test_alloc_crc_dc_fixture(test, adev); + fixture->dm_state->crc_skip_count = 2; + fixture->get_crc_return = false; + acrtc->base.state = &fixture->dm_state->base; + acrtc->dm_irq_params.crc_src = AMDGPU_DM_PIPE_CRC_SOURCE_CRTC; + dm_test_crc_dc_ctx = fixture; + + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + dm_test_crc_dc_ctx = NULL; + + KUNIT_EXPECT_TRUE(test, fixture->get_crc_called); + KUNIT_EXPECT_EQ(test, fixture->dm_state->crc_skip_count, 2); +} + /** * dm_test_need_dp_aux() - Test dm_need_dp_aux(). * @test: KUnit test context. @@ -818,6 +944,12 @@ static struct kunit_case dm_crc_test_cases[] = { KUNIT_CASE(dm_test_crtc_set_crc_source_none_no_stream), KUNIT_CASE(dm_test_crtc_set_crc_source_none_commit), KUNIT_CASE(dm_test_crtc_set_crc_source_dprx_no_connector), + /* amdgpu_dm_crtc_handle_crc_irq() */ + KUNIT_CASE(dm_test_crtc_handle_crc_irq_early_returns), + KUNIT_CASE(dm_test_crtc_handle_crc_irq_disabled_source), + KUNIT_CASE(dm_test_crtc_handle_crc_irq_skips_initial_frames), + KUNIT_CASE(dm_test_crtc_handle_crc_irq_dprx_after_skip), + KUNIT_CASE(dm_test_crtc_handle_crc_irq_get_crc_fails), /* dm_need_dp_aux() */ KUNIT_CASE(dm_test_need_dp_aux), /* dm_crc_source_should_start_dprx() */ -- cgit v1.2.3 From a79f5e4c0c7e66848a49c42465b00049625394db Mon Sep 17 00:00:00 2001 From: Aurabindo Pillai Date: Wed, 8 Jul 2026 16:43:02 -0400 Subject: drm/amd/display: Adjust the structure dml2_dchub_watermark_regs Adjust the structure dml2_dchub_watermark_regs for future usage Reviewed-by: Austin Zheng Signed-off-by: Aurabindo Pillai Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h index 5669be0a7340..799e72243418 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h @@ -171,8 +171,15 @@ struct dml2_dchub_watermark_regs { uint32_t sr_exit_low_power; uint32_t uclk_pstate; uint32_t fclk_pstate; + union { uint32_t temp_read_or_ppt; + uint32_t temp_read; + }; + uint32_t ppt; + union { uint32_t usr; + uint32_t buffer_fullness; + }; /* qos */ uint32_t refcyc_per_trip_to_mem; uint32_t refcyc_per_meta_trip_to_mem; -- cgit v1.2.3 From 46011ff577cfbcdceef53bc6796660a5fcac5e5f Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Wed, 24 Jun 2026 16:50:25 -0400 Subject: drm/amd/display: Add mode helper tests for connector Add KUnit coverage for the connector mode helpers: amdgpu_dm_connector_funcs_force(), dm_validate_stream_and_context(), amdgpu_dm_connector_to_encoder(), amdgpu_dm_get_native_mode(), amdgpu_dm_create_common_mode(), amdgpu_dm_connector_add_common_modes(), amdgpu_dm_connector_ddc_get_modes(), add_fs_modes() and amdgpu_dm_connector_add_freesync_modes(). Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 27 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 15 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 309 +++++++++++++++++++++ 3 files changed, 342 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 6e07350b86ef..7feb3105f8ba 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1952,7 +1952,7 @@ amdgpu_dm_connector_late_register(struct drm_connector *connector) } EXPORT_IF_KUNIT(amdgpu_dm_connector_late_register); -static void amdgpu_dm_connector_funcs_force(struct drm_connector *connector) +STATIC_IFN_KUNIT void amdgpu_dm_connector_funcs_force(struct drm_connector *connector) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); struct dc_link *dc_link = aconnector->dc_link; @@ -1988,6 +1988,7 @@ static void amdgpu_dm_connector_funcs_force(struct drm_connector *connector) &dc_em_sink->edid_caps); } } +EXPORT_IF_KUNIT(amdgpu_dm_connector_funcs_force); static const struct drm_connector_funcs amdgpu_dm_connector_funcs = { .reset = amdgpu_dm_connector_funcs_reset, @@ -2072,7 +2073,7 @@ STATIC_IFN_KUNIT void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) } EXPORT_IF_KUNIT(handle_edid_mgmt); -static enum dc_status dm_validate_stream_and_context(struct dc *dc, +STATIC_IFN_KUNIT enum dc_status dm_validate_stream_and_context(struct dc *dc, struct dc_stream_state *stream) { enum dc_status dc_result = DC_ERROR_UNEXPECTED; @@ -2134,6 +2135,7 @@ cleanup: return dc_result; } +EXPORT_IF_KUNIT(dm_validate_stream_and_context); static enum dc_status dm_validate_stream_color_format(const struct drm_connector_state *drm_state, @@ -2539,7 +2541,7 @@ STATIC_IFN_KUNIT int to_drm_connector_type(enum signal_type st, uint32_t connect } EXPORT_IF_KUNIT(to_drm_connector_type); -static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector) +STATIC_IFN_KUNIT struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector) { struct drm_encoder *encoder; @@ -2549,8 +2551,9 @@ static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector * return NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_to_encoder); -static void amdgpu_dm_get_native_mode(struct drm_connector *connector) +STATIC_IFN_KUNIT void amdgpu_dm_get_native_mode(struct drm_connector *connector) { struct drm_encoder *encoder; struct amdgpu_encoder *amdgpu_encoder; @@ -2578,8 +2581,9 @@ static void amdgpu_dm_get_native_mode(struct drm_connector *connector) } } +EXPORT_IF_KUNIT(amdgpu_dm_get_native_mode); -static struct drm_display_mode * +STATIC_IFN_KUNIT struct drm_display_mode * amdgpu_dm_create_common_mode(struct drm_encoder *encoder, const char *name, int hdisplay, int vdisplay) @@ -2602,6 +2606,7 @@ amdgpu_dm_create_common_mode(struct drm_encoder *encoder, return mode; } +EXPORT_IF_KUNIT(amdgpu_dm_create_common_mode); static const struct amdgpu_dm_mode_size { char name[DRM_DISPLAY_MODE_LEN]; @@ -2621,7 +2626,7 @@ static const struct amdgpu_dm_mode_size { {"1920x1200", 1920, 1200} }; -static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, +STATIC_IFN_KUNIT void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, struct drm_connector *connector) { struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); @@ -2669,6 +2674,7 @@ static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, amdgpu_dm_connector->num_modes++; } } +EXPORT_IF_KUNIT(amdgpu_dm_connector_add_common_modes); void amdgpu_set_panel_orientation(struct drm_connector *connector) { @@ -2700,7 +2706,7 @@ void amdgpu_set_panel_orientation(struct drm_connector *connector) native_mode->vdisplay); } -static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, +STATIC_IFN_KUNIT void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, const struct drm_edid *drm_edid) { struct amdgpu_dm_connector *amdgpu_dm_connector = @@ -2732,6 +2738,7 @@ static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, amdgpu_dm_connector->num_modes = 0; } } +EXPORT_IF_KUNIT(amdgpu_dm_connector_ddc_get_modes); STATIC_IFN_KUNIT bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, struct drm_display_mode *mode) @@ -2747,7 +2754,7 @@ STATIC_IFN_KUNIT bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, } EXPORT_IF_KUNIT(is_duplicate_mode); -static uint add_fs_modes(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT uint add_fs_modes(struct amdgpu_dm_connector *aconnector) { const struct drm_display_mode *m; struct drm_display_mode *new_mode; @@ -2822,8 +2829,9 @@ static uint add_fs_modes(struct amdgpu_dm_connector *aconnector) out: return new_modes_count; } +EXPORT_IF_KUNIT(add_fs_modes); -static void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector, +STATIC_IFN_KUNIT void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector, const struct drm_edid *drm_edid) { struct amdgpu_dm_connector *amdgpu_dm_connector = @@ -2846,6 +2854,7 @@ static void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connect amdgpu_dm_connector->num_modes += add_fs_modes(amdgpu_dm_connector); } +EXPORT_IF_KUNIT(amdgpu_dm_connector_add_freesync_modes); static int amdgpu_dm_connector_get_modes(struct drm_connector *connector) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 1cce1e8863c6..082e613de3f1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -146,6 +146,21 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, uint32_t link_index); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void amdgpu_dm_connector_funcs_force(struct drm_connector *connector); +enum dc_status dm_validate_stream_and_context(struct dc *dc, + struct dc_stream_state *stream); +struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector); +void amdgpu_dm_get_native_mode(struct drm_connector *connector); +struct drm_display_mode *amdgpu_dm_create_common_mode(struct drm_encoder *encoder, + const char *name, + int hdisplay, int vdisplay); +void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, + struct drm_connector *connector); +void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, + const struct drm_edid *drm_edid); +uint add_fs_modes(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector, + const struct drm_edid *drm_edid); void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector); void create_eml_sink(struct amdgpu_dm_connector *aconnector); void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index e8335963050f..d9aaf03dd470 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -11,8 +11,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -4379,6 +4381,291 @@ static void dm_test_handle_edid_mgmt_non_dp_leaves_caps(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)ctx->link->verified_link_cap.link_rate, 0); } +/* + * Context for the connector funcs / modes tests: a managed DRM device with a + * registered connector and a managed encoder attached to it, so helpers that + * walk connector->encoder relationships resolve correctly. + */ +struct dm_test_modes_ctx { + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_encoder *aenc; +}; + +static struct dm_test_modes_ctx * +dm_test_modes_ctx_alloc(struct kunit *test, int connector_type) +{ + struct dm_test_modes_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->drm = dm_test_alloc_drm(test); + ctx->aconnector = dm_test_add_connector(test, ctx->drm, connector_type); + + ctx->aenc = kunit_kzalloc(test, sizeof(*ctx->aenc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aenc); + KUNIT_ASSERT_EQ(test, + drmm_encoder_init(ctx->drm, &ctx->aenc->base, NULL, + DRM_MODE_ENCODER_TMDS, NULL), 0); + KUNIT_ASSERT_EQ(test, + drm_connector_attach_encoder(&ctx->aconnector->base, + &ctx->aenc->base), 0); + + return ctx; +} + +/** + * dm_test_funcs_force_no_edid - Test force() leaves drm_edid NULL when no EDID + * @test: The KUnit test context + * + * A headless force-on DisplayPort connector reads no EDID, so the cached + * drm_edid pointer must stay NULL after the force callback runs. + */ +static void dm_test_funcs_force_no_edid(struct kunit *test) +{ + struct dm_test_edid_ctx *ctx = + dm_test_edid_ctx_alloc(test, DRM_MODE_CONNECTOR_DisplayPort); + + amdgpu_dm_connector_funcs_force(&ctx->aconnector->base); + + KUNIT_EXPECT_NULL(test, ctx->aconnector->drm_edid); +} + +/** + * dm_test_validate_stream_null_stream - Test NULL stream returns unexpected + * @test: The KUnit test context + * + * With a NULL stream the validation jumps straight to cleanup without ever + * dereferencing the dc handle and reports DC_ERROR_UNEXPECTED. + */ +static void dm_test_validate_stream_null_stream(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, + (int)dm_validate_stream_and_context(NULL, NULL), + (int)DC_ERROR_UNEXPECTED); +} + +/** + * dm_test_to_encoder_no_encoder - Test connector with no encoder returns NULL + * @test: The KUnit test context + */ +static void dm_test_to_encoder_no_encoder(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector = + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_connector_to_encoder(&aconnector->base)); +} + +/** + * dm_test_to_encoder_returns_attached - Test the attached encoder is returned + * @test: The KUnit test context + */ +static void dm_test_to_encoder_returns_attached(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA); + + KUNIT_EXPECT_PTR_EQ(test, + amdgpu_dm_connector_to_encoder(&ctx->aconnector->base), + &ctx->aenc->base); +} + +/** + * dm_test_native_mode_no_encoder - Test native mode resolution is a no-op + * @test: The KUnit test context + * + * Without an encoder there is nothing to copy into, so the call must return + * cleanly without dereferencing a NULL encoder. + */ +static void dm_test_native_mode_no_encoder(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector = + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + + amdgpu_dm_get_native_mode(&aconnector->base); +} + +/** + * dm_test_native_mode_empty_probed_zeroes_clock - Test empty probed list clears mode + * @test: The KUnit test context + * + * With no probed modes there is no preferred mode to copy, so the encoder's + * native mode is memset to zero (clock becomes 0). + */ +static void dm_test_native_mode_empty_probed_zeroes_clock(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP); + + ctx->aenc->native_mode.clock = 148500; + + amdgpu_dm_get_native_mode(&ctx->aconnector->base); + + KUNIT_EXPECT_EQ(test, ctx->aenc->native_mode.clock, 0); +} + +/** + * dm_test_native_mode_copies_preferred - Test the preferred mode is copied + * @test: The KUnit test context + * + * The preferred probed mode is duplicated into the encoder's native mode. + */ +static void dm_test_native_mode_copies_preferred(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP); + struct drm_display_mode *mode; + + mode = drm_mode_create(ctx->drm); + KUNIT_ASSERT_NOT_NULL(test, mode); + mode->type = DRM_MODE_TYPE_PREFERRED; + mode->clock = 148500; + mode->hdisplay = 1920; + mode->vdisplay = 1080; + drm_mode_probed_add(&ctx->aconnector->base, mode); + + amdgpu_dm_get_native_mode(&ctx->aconnector->base); + + KUNIT_EXPECT_EQ(test, ctx->aenc->native_mode.hdisplay, 1920); + KUNIT_EXPECT_EQ(test, ctx->aenc->native_mode.vdisplay, 1080); + KUNIT_EXPECT_EQ(test, ctx->aenc->native_mode.clock, 148500); +} + +/** + * dm_test_create_common_mode_overrides - Test common mode inherits native timing + * @test: The KUnit test context + * + * A new common mode takes its pixel clock and porches from the encoder's + * native mode but overrides the visible resolution and clears PREFERRED. + */ +static void dm_test_create_common_mode_overrides(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP); + struct drm_display_mode *mode; + + ctx->aenc->native_mode.clock = 148500; + ctx->aenc->native_mode.htotal = 2200; + ctx->aenc->native_mode.type = DRM_MODE_TYPE_PREFERRED; + + mode = amdgpu_dm_create_common_mode(&ctx->aenc->base, "800x600", + 800, 600); + KUNIT_ASSERT_NOT_NULL(test, mode); + + KUNIT_EXPECT_EQ(test, mode->hdisplay, 800); + KUNIT_EXPECT_EQ(test, mode->vdisplay, 600); + KUNIT_EXPECT_EQ(test, mode->clock, 148500); + KUNIT_EXPECT_EQ(test, mode->htotal, 2200); + KUNIT_EXPECT_FALSE(test, mode->type & DRM_MODE_TYPE_PREFERRED); + KUNIT_EXPECT_STREQ(test, mode->name, "800x600"); + + drm_mode_destroy(ctx->drm, mode); +} + +/** + * dm_test_add_common_modes_non_edp_noop - Test non-eDP/LVDS adds no modes + * @test: The KUnit test context + * + * Common scaled modes are only added for eDP/LVDS panels; an HDMI connector + * is left untouched. + */ +static void dm_test_add_common_modes_non_edp_noop(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_HDMIA); + + ctx->aenc->native_mode.hdisplay = 1920; + ctx->aenc->native_mode.vdisplay = 1200; + + amdgpu_dm_connector_add_common_modes(&ctx->aenc->base, + &ctx->aconnector->base); + + KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 0); +} + +/** + * dm_test_add_common_modes_edp_adds - Test eDP adds the smaller common modes + * @test: The KUnit test context + * + * For an eDP panel with a 1920x1200 native mode every common mode strictly + * smaller than the native one is added (10 of the 11 entries). + */ +static void dm_test_add_common_modes_edp_adds(struct kunit *test) +{ + struct dm_test_modes_ctx *ctx = + dm_test_modes_ctx_alloc(test, DRM_MODE_CONNECTOR_eDP); + + ctx->aenc->native_mode.hdisplay = 1920; + ctx->aenc->native_mode.vdisplay = 1200; + + amdgpu_dm_connector_add_common_modes(&ctx->aenc->base, + &ctx->aconnector->base); + + KUNIT_EXPECT_EQ(test, ctx->aconnector->num_modes, 10); +} + +/** + * dm_test_ddc_get_modes_null_edid - Test a NULL EDID resets the mode count + * @test: The KUnit test context + */ +static void dm_test_ddc_get_modes_null_edid(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector = + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + + aconnector->num_modes = 5; + + amdgpu_dm_connector_ddc_get_modes(&aconnector->base, NULL); + + KUNIT_EXPECT_EQ(test, aconnector->num_modes, 0); +} + +/** + * dm_test_add_fs_modes_no_preferred_mode - Test no preferred mode yields no modes + * @test: The KUnit test context + * + * A writeback connector has no highest-refresh-rate mode, so add_fs_modes() + * cannot build any FreeSync video modes and returns 0. + */ +static void dm_test_add_fs_modes_no_preferred_mode(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; + + KUNIT_EXPECT_EQ(test, (int)add_fs_modes(aconnector), 0); +} + +/** + * dm_test_add_freesync_modes_null_edid_noop - Test NULL EDID adds no modes + * @test: The KUnit test context + * + * Without an EDID the FreeSync video modes cannot be derived, so the mode + * count is left unchanged. + */ +static void dm_test_add_freesync_modes_null_edid_noop(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->num_modes = 7; + + amdgpu_dm_connector_add_freesync_modes(&aconnector->base, NULL); + + KUNIT_EXPECT_EQ(test, aconnector->num_modes, 7); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -4608,6 +4895,28 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { /* handle_edid_mgmt */ KUNIT_CASE(dm_test_handle_edid_mgmt_dp_sets_link_caps), KUNIT_CASE(dm_test_handle_edid_mgmt_non_dp_leaves_caps), + /* amdgpu_dm_connector_funcs_force */ + KUNIT_CASE(dm_test_funcs_force_no_edid), + /* dm_validate_stream_and_context */ + KUNIT_CASE(dm_test_validate_stream_null_stream), + /* amdgpu_dm_connector_to_encoder */ + KUNIT_CASE(dm_test_to_encoder_no_encoder), + KUNIT_CASE(dm_test_to_encoder_returns_attached), + /* amdgpu_dm_get_native_mode */ + KUNIT_CASE(dm_test_native_mode_no_encoder), + KUNIT_CASE(dm_test_native_mode_empty_probed_zeroes_clock), + KUNIT_CASE(dm_test_native_mode_copies_preferred), + /* amdgpu_dm_create_common_mode */ + KUNIT_CASE(dm_test_create_common_mode_overrides), + /* amdgpu_dm_connector_add_common_modes */ + KUNIT_CASE(dm_test_add_common_modes_non_edp_noop), + KUNIT_CASE(dm_test_add_common_modes_edp_adds), + /* amdgpu_dm_connector_ddc_get_modes */ + KUNIT_CASE(dm_test_ddc_get_modes_null_edid), + /* add_fs_modes */ + KUNIT_CASE(dm_test_add_fs_modes_no_preferred_mode), + /* amdgpu_dm_connector_add_freesync_modes */ + KUNIT_CASE(dm_test_add_freesync_modes_null_edid_noop), {} }; -- cgit v1.2.3 From 6be28c11dcb739cd3b2782ec36d035879805fedc Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Thu, 25 Jun 2026 10:55:46 -0400 Subject: drm/amd/display: Add i2c and EDID parsing tests for connector Add KUnit coverage for the i2c and EDID parsing helpers: amdgpu_dm_i2c_func(), amdgpu_dm_i2c_xfer(), get_amd_vsdb(), parse_hdmi_amd_vsdb() and parse_edid_displayid_vrr(). Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 15 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 10 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 249 +++++++++++++++++++++ 3 files changed, 269 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 7feb3105f8ba..01352fc9445e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -3087,7 +3087,7 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, } } -static int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap, +STATIC_IFN_KUNIT int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) { struct amdgpu_i2c_adapter *i2c = i2c_get_adapdata(i2c_adap); @@ -3131,11 +3131,13 @@ static int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap, kfree(cmd.payloads); return result; } +EXPORT_IF_KUNIT(amdgpu_dm_i2c_xfer); -static u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap) +STATIC_IFN_KUNIT u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } +EXPORT_IF_KUNIT(amdgpu_dm_i2c_func); static const struct i2c_algorithm amdgpu_dm_i2c_algo = { .master_xfer = amdgpu_dm_i2c_xfer, @@ -3496,7 +3498,7 @@ static bool parse_edid_cea(struct amdgpu_dm_connector *aconnector, return ret; } -static void parse_edid_displayid_vrr(struct drm_connector *connector, +STATIC_IFN_KUNIT void parse_edid_displayid_vrr(struct drm_connector *connector, const struct edid *edid) { u8 *edid_ext = NULL; @@ -3538,8 +3540,9 @@ static void parse_edid_displayid_vrr(struct drm_connector *connector, j++; } } +EXPORT_IF_KUNIT(parse_edid_displayid_vrr); -static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, +STATIC_IFN_KUNIT int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, struct amdgpu_hdmi_vsdb_info *vsdb_info) { struct drm_connector *connector = &aconnector->base; @@ -3549,8 +3552,9 @@ static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, return connector->display_info.amd_vsdb.version != 0; } +EXPORT_IF_KUNIT(get_amd_vsdb); -static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, +STATIC_IFN_KUNIT int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, const struct edid *edid, struct amdgpu_hdmi_vsdb_info *vsdb_info) { @@ -3581,6 +3585,7 @@ static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, return valid_vsdb_found ? i : -ENODEV; } +EXPORT_IF_KUNIT(parse_hdmi_amd_vsdb); /** * amdgpu_dm_update_freesync_caps - Update Freesync capabilities diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 082e613de3f1..13c54229d72c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -146,6 +146,16 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, uint32_t link_index); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num); +u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap); +void parse_edid_displayid_vrr(struct drm_connector *connector, + const struct edid *edid); +int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, + struct amdgpu_hdmi_vsdb_info *vsdb_info); +int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, + const struct edid *edid, + struct amdgpu_hdmi_vsdb_info *vsdb_info); void amdgpu_dm_connector_funcs_force(struct drm_connector *connector); enum dc_status dm_validate_stream_and_context(struct dc *dc, struct dc_stream_state *stream); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index d9aaf03dd470..c56efbc1f26b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "dc.h" #include "amdgpu.h" @@ -4666,6 +4667,239 @@ static void dm_test_add_freesync_modes_null_edid_noop(struct kunit *test) KUNIT_EXPECT_EQ(test, aconnector->num_modes, 7); } +/* EDID extension block tag values (avoids pulling in private drm headers). */ +#define DM_TEST_CEA_EXT 0x02 +#define DM_TEST_DISPLAYID_EXT 0x70 + +/** + * dm_test_i2c_func_returns_flags - Test the i2c functionality flags + * @test: The KUnit test context + * + * The algorithm advertises plain I2C plus emulated SMBUS regardless of the + * adapter argument, which it never dereferences. + */ +static void dm_test_i2c_func_returns_flags(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, amdgpu_dm_i2c_func(NULL), + I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL); +} + +/** + * dm_test_i2c_xfer_no_ddc_pin - Test transfers without a DDC pin are rejected + * @test: The KUnit test context + * + * When the backing ddc_service has no ddc_pin the transfer bails out early + * with -EIO before touching the message buffers or the dc handle. + */ +static void dm_test_i2c_xfer_no_ddc_pin(struct kunit *test) +{ + struct amdgpu_i2c_adapter *i2c; + struct ddc_service *ddc; + + i2c = kunit_kzalloc(test, sizeof(*i2c), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, i2c); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ddc); + + i2c->ddc_service = ddc; + i2c_set_adapdata(&i2c->base, i2c); + + /* ddc->ddc_pin is NULL -> transfer is rejected with -EIO. */ + KUNIT_EXPECT_EQ(test, amdgpu_dm_i2c_xfer(&i2c->base, NULL, 0), -EIO); +} + +/** + * dm_test_get_amd_vsdb_unsupported - Test a zero VSDB version reports no support + * @test: The KUnit test context + */ +static void dm_test_get_amd_vsdb_unsupported(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.display_info.amd_vsdb.version = 0; + aconnector->base.display_info.amd_vsdb.replay_mode = false; + + KUNIT_EXPECT_EQ(test, get_amd_vsdb(aconnector, &vsdb_info), 0); + KUNIT_EXPECT_EQ(test, vsdb_info.amd_vsdb_version, 0); +} + +/** + * dm_test_get_amd_vsdb_supported - Test a non-zero VSDB version is reported + * @test: The KUnit test context + * + * The display info's VSDB version and replay mode are copied out and a + * non-zero version reports support. + */ +static void dm_test_get_amd_vsdb_supported(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.display_info.amd_vsdb.version = 2; + aconnector->base.display_info.amd_vsdb.replay_mode = true; + + KUNIT_EXPECT_EQ(test, get_amd_vsdb(aconnector, &vsdb_info), 1); + KUNIT_EXPECT_EQ(test, vsdb_info.amd_vsdb_version, 2); + KUNIT_EXPECT_TRUE(test, vsdb_info.replay_mode); +} + +/** + * dm_test_parse_hdmi_amd_vsdb_null_edid - Test NULL EDID returns -ENODEV + * @test: The KUnit test context + */ +static void dm_test_parse_hdmi_amd_vsdb_null_edid(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + KUNIT_EXPECT_EQ(test, + parse_hdmi_amd_vsdb(aconnector, NULL, &vsdb_info), + -ENODEV); +} + +/** + * dm_test_parse_hdmi_amd_vsdb_no_extensions - Test EDID without extensions + * @test: The KUnit test context + * + * An EDID that declares no extension blocks has no CEA block to parse. + */ +static void dm_test_parse_hdmi_amd_vsdb_no_extensions(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + struct edid *edid; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid); + + edid->extensions = 0; + + KUNIT_EXPECT_EQ(test, + parse_hdmi_amd_vsdb(aconnector, edid, &vsdb_info), + -ENODEV); +} + +/** + * dm_test_parse_hdmi_amd_vsdb_no_cea_ext - Test EDID with no CEA extension + * @test: The KUnit test context + * + * An extension block that is not a CEA block leaves no VSDB to parse. + */ +static void dm_test_parse_hdmi_amd_vsdb_no_cea_ext(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + struct edid *edid; + u8 *raw; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + /* Base block + one extension block that is NOT a CEA extension. */ + raw = kunit_kzalloc(test, 2 * EDID_LENGTH, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, raw); + edid = (struct edid *)raw; + edid->extensions = 1; + raw[EDID_LENGTH] = DM_TEST_DISPLAYID_EXT; + + KUNIT_EXPECT_EQ(test, + parse_hdmi_amd_vsdb(aconnector, edid, &vsdb_info), + -ENODEV); +} + +/** + * dm_test_parse_displayid_vrr_null_edid - Test NULL EDID leaves range untouched + * @test: The KUnit test context + */ +static void dm_test_parse_displayid_vrr_null_edid(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + parse_edid_displayid_vrr(connector, NULL); + + KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.max_vfreq, 0); + KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.min_vfreq, 0); +} + +/** + * dm_test_parse_displayid_vrr_no_displayid - Test EDID without a DisplayID ext + * @test: The KUnit test context + * + * Without a DisplayID extension block there is no dynamic range to extract. + */ +static void dm_test_parse_displayid_vrr_no_displayid(struct kunit *test) +{ + struct drm_connector *connector; + struct edid *edid; + u8 *raw; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + raw = kunit_kzalloc(test, 2 * EDID_LENGTH, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, raw); + edid = (struct edid *)raw; + edid->extensions = 1; + raw[EDID_LENGTH] = DM_TEST_CEA_EXT; + + parse_edid_displayid_vrr(connector, edid); + + KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.max_vfreq, 0); +} + +/** + * dm_test_parse_displayid_vrr_sets_range - Test a DisplayID VRR block is parsed + * @test: The KUnit test context + * + * A DisplayID dynamic video timing range descriptor populates the connector's + * monitor refresh range. + */ +static void dm_test_parse_displayid_vrr_sets_range(struct kunit *test) +{ + struct drm_connector *connector; + struct edid *edid; + u8 *raw, *ext; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + raw = kunit_kzalloc(test, 2 * EDID_LENGTH, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, raw); + edid = (struct edid *)raw; + edid->extensions = 1; + + ext = raw + EDID_LENGTH; + ext[0] = DM_TEST_DISPLAYID_EXT; + /* + * DisplayID dynamic video timing range descriptor, parsed from offset + * 1: tag 0x25, flags 0 (single-byte max), payload length 9, then the + * min/max vfreq bytes. + */ + ext[1] = 0x25; + ext[2] = 0x00; + ext[3] = 9; + ext[10] = 40; + ext[11] = 144; + + parse_edid_displayid_vrr(connector, edid); + + KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.min_vfreq, 40); + KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.max_vfreq, 144); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -4917,6 +5151,21 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_add_fs_modes_no_preferred_mode), /* amdgpu_dm_connector_add_freesync_modes */ KUNIT_CASE(dm_test_add_freesync_modes_null_edid_noop), + /* amdgpu_dm_i2c_func */ + KUNIT_CASE(dm_test_i2c_func_returns_flags), + /* amdgpu_dm_i2c_xfer */ + KUNIT_CASE(dm_test_i2c_xfer_no_ddc_pin), + /* get_amd_vsdb */ + KUNIT_CASE(dm_test_get_amd_vsdb_unsupported), + KUNIT_CASE(dm_test_get_amd_vsdb_supported), + /* parse_hdmi_amd_vsdb */ + KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_null_edid), + KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_no_extensions), + KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_no_cea_ext), + /* parse_edid_displayid_vrr */ + KUNIT_CASE(dm_test_parse_displayid_vrr_null_edid), + KUNIT_CASE(dm_test_parse_displayid_vrr_no_displayid), + KUNIT_CASE(dm_test_parse_displayid_vrr_sets_range), {} }; -- cgit v1.2.3 From ec3938e1a9bd741a80074264b4f702cc8af0b1dc Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Thu, 25 Jun 2026 11:14:25 -0400 Subject: drm/amd/display: Add mode validation and CEC tests for connector Add KUnit coverage for connector mode validation and HDMI CEC: mode_valid rejects interlaced and doublescan modes, set_edid with no notifier, and the S3 suspend/resume CEC handlers. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 3 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 105 +++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 01352fc9445e..0b97fbaf8f5a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -356,6 +356,7 @@ void amdgpu_dm_hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector) cec_notifier_set_phys_addr(n, connector->display_info.source_physical_address); } +EXPORT_IF_KUNIT(amdgpu_dm_hdmi_cec_set_edid); void amdgpu_dm_s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend) { @@ -376,6 +377,7 @@ void amdgpu_dm_s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend) } drm_connector_list_iter_end(&conn_iter); } +EXPORT_IF_KUNIT(amdgpu_dm_s3_handle_hdmi_cec); struct drm_connector * @@ -2327,6 +2329,7 @@ fail: /* TODO: error handling*/ return result; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_mode_valid); int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state, struct dc_info_packet *out) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index c56efbc1f26b..ee20f2c2a80b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -4900,6 +4900,103 @@ static void dm_test_parse_displayid_vrr_sets_range(struct kunit *test) KUNIT_EXPECT_EQ(test, connector->display_info.monitor_range.max_vfreq, 144); } +/** + * dm_test_mode_valid_interlace_rejected - Test interlaced modes are rejected + * @test: The KUnit test context + * + * Interlaced modes are rejected up front with MODE_ERROR before any sink or + * stream validation is attempted. + */ +static void dm_test_mode_valid_interlace_rejected(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode *mode; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mode); + + mode->flags = DRM_MODE_FLAG_INTERLACE; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_connector_mode_valid(&aconnector->base, mode), + MODE_ERROR); +} + +/** + * dm_test_mode_valid_dblscan_rejected - Test doublescan modes are rejected + * @test: The KUnit test context + * + * Doublescan modes are rejected up front with MODE_ERROR. + */ +static void dm_test_mode_valid_dblscan_rejected(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode *mode; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mode); + + mode->flags = DRM_MODE_FLAG_DBLSCAN; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_connector_mode_valid(&aconnector->base, mode), + MODE_ERROR); +} + +/** + * dm_test_hdmi_cec_set_edid_no_notifier - Test the no-notifier no-op path + * @test: The KUnit test context + * + * With aconnector->notifier NULL the function returns early and must not crash. + */ +static void dm_test_hdmi_cec_set_edid_no_notifier(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + amdgpu_dm_hdmi_cec_set_edid(aconnector); +} + +/** + * dm_test_s3_handle_hdmi_cec_suspend - Test the suspend pass over connectors + * @test: The KUnit test context + * + * Suspend iterates all connectors, skipping writeback ones and calling the + * unset path on the rest; with NULL notifiers this is a crash-free no-op. + */ +static void dm_test_s3_handle_hdmi_cec_suspend(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_WRITEBACK); + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + + amdgpu_dm_s3_handle_hdmi_cec(drm, true); +} + +/** + * dm_test_s3_handle_hdmi_cec_resume - Test the resume pass over connectors + * @test: The KUnit test context + * + * Resume iterates all connectors, skipping writeback ones and calling the set + * path on the rest; with NULL notifiers this is a crash-free no-op. + */ +static void dm_test_s3_handle_hdmi_cec_resume(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_WRITEBACK); + dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + + amdgpu_dm_s3_handle_hdmi_cec(drm, false); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -5166,6 +5263,14 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_parse_displayid_vrr_null_edid), KUNIT_CASE(dm_test_parse_displayid_vrr_no_displayid), KUNIT_CASE(dm_test_parse_displayid_vrr_sets_range), + /* amdgpu_dm_connector_mode_valid */ + KUNIT_CASE(dm_test_mode_valid_interlace_rejected), + KUNIT_CASE(dm_test_mode_valid_dblscan_rejected), + /* amdgpu_dm_hdmi_cec_set_edid */ + KUNIT_CASE(dm_test_hdmi_cec_set_edid_no_notifier), + /* amdgpu_dm_s3_handle_hdmi_cec */ + KUNIT_CASE(dm_test_s3_handle_hdmi_cec_suspend), + KUNIT_CASE(dm_test_s3_handle_hdmi_cec_resume), {} }; -- cgit v1.2.3 From 5af478e1ddbd86ef8a66cb0035613ad80581b06a Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Thu, 25 Jun 2026 11:25:53 -0400 Subject: drm/amd/display: Add stream validation tests for connector Add KUnit coverage for amdgpu_dm_create_validate_stream_for_sink() and amdgpu_dm_connector_funcs_update_after_detect(): null dm_state, MST no-op, and unchanged sink handling. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 2 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 68 ++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 0b97fbaf8f5a..b22ded941391 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -659,6 +659,7 @@ void amdgpu_dm_update_connector_after_detect( if (!drm_kms_helper_is_poll_worker()) mutex_unlock(&dev->mode_config.mutex); } +EXPORT_IF_KUNIT(amdgpu_dm_update_connector_after_detect); enum dc_color_depth amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *connector, @@ -2276,6 +2277,7 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector, return stream; } +EXPORT_IF_KUNIT(amdgpu_dm_create_validate_stream_for_sink); enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, const struct drm_display_mode *mode) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index ee20f2c2a80b..75f6be829f24 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -4997,6 +4997,69 @@ static void dm_test_s3_handle_hdmi_cec_resume(struct kunit *test) amdgpu_dm_s3_handle_hdmi_cec(drm, false); } +/** + * dm_test_create_validate_stream_null_dm_state - Test NULL state returns NULL + * @test: The KUnit test context + * + * Without a connector state there is nothing to validate against, so the + * helper bails out with NULL before touching the dc handle. + */ +static void dm_test_create_validate_stream_null_dm_state(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_create_validate_stream_for_sink(&aconnector->base, + NULL, NULL, NULL)); +} + +/** + * dm_test_update_after_detect_mst_noop - Test MST connectors are left to drm_mst + * @test: The KUnit test context + * + * An MST connector is handled by the drm_mst framework, so the function + * returns immediately and never dereferences the (NULL) dc_link. + */ +static void dm_test_update_after_detect_mst_noop(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->mst_mgr.mst_state = true; + + amdgpu_dm_update_connector_after_detect(aconnector); +} + +/** + * dm_test_update_after_detect_sink_unchanged - Test the short-pulse no-op path + * @test: The KUnit test context + * + * When the link reports no local sink and the connector already has no + * dc_sink, the "sink didn't change" path returns without touching DC. + */ +static void dm_test_update_after_detect_sink_unchanged(struct kunit *test) +{ + struct drm_device *drm = dm_test_alloc_drm(test); + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + aconnector = dm_test_add_connector(test, drm, DRM_MODE_CONNECTOR_HDMIA); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + aconnector->dc_link = link; + + /* link->local_sink and aconnector->dc_sink are both NULL. */ + amdgpu_dm_update_connector_after_detect(aconnector); + + KUNIT_EXPECT_NULL(test, aconnector->dc_sink); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -5271,6 +5334,11 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { /* amdgpu_dm_s3_handle_hdmi_cec */ KUNIT_CASE(dm_test_s3_handle_hdmi_cec_suspend), KUNIT_CASE(dm_test_s3_handle_hdmi_cec_resume), + /* amdgpu_dm_create_validate_stream_for_sink */ + KUNIT_CASE(dm_test_create_validate_stream_null_dm_state), + /* amdgpu_dm_update_connector_after_detect */ + KUNIT_CASE(dm_test_update_after_detect_mst_noop), + KUNIT_CASE(dm_test_update_after_detect_sink_unchanged), {} }; -- cgit v1.2.3 From ba8b08aaa8459b02e325611d2c103b196ca1a2e1 Mon Sep 17 00:00:00 2001 From: Aurabindo Pillai Date: Wed, 8 Jul 2026 22:40:17 -0400 Subject: drm/amd/display: Adjust structure dml2_display_dlg_regs Adjust structure dml2_display_dlg_regs for future use Reviewed-by: Dillon Varone Signed-off-by: Aurabindo Pillai Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h index 799e72243418..5a5f7b92c324 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/dml_top_dchub_registers.h @@ -46,6 +46,9 @@ struct dml2_display_dlg_regs { uint32_t dst_y_delta_drq_limit; uint32_t refcyc_per_vm_dmdata; uint32_t dmdata_dl_delta; + uint32_t dst_y_svp_drq_limit; + uint32_t force_prefetch_to_vblank; + uint32_t force_cursor_to_disp_pref; // MRQ uint32_t refcyc_per_meta_chunk_vblank_l; -- cgit v1.2.3 From e0de58ca36607bf790bdad0af70665ba2c135fda Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 7 Jul 2026 11:29:10 -0400 Subject: drm/amd/display: Revert Fix DMSS not triggering for HDR to SDR transition Reverting this commit as it causes a regression lighting up eDP panels: This reverts commit 1f07e9fddc4a ("drm/amd/display: Fix DMSS not triggering for HDR to SDR transition") Fixes: 1f07e9fddc4a ("drm/amd/display: Fix DMSS not triggering for HDR to SDR transition") Reviewed-by: Joshua Aberback Reviewed-by: Yihan Zhu Signed-off-by: Justin Chen Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/hwss/dce110/dce110_hwseq.c | 12 +++-------- .../drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c | 21 ++++++-------------- .../drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c | 7 ++----- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 23 ++++++---------------- .../gpu/drm/amd/display/modules/power/power_psr.c | 7 ------- .../drm/amd/display/modules/power/power_replay.c | 7 ------- 6 files changed, 17 insertions(+), 60 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index 4830a0d94177..262982ca5ad9 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -1286,9 +1286,7 @@ void dce110_blank_stream(struct pipe_ctx *pipe_ctx) return; if (link->local_sink && link->local_sink->sink_signal == SIGNAL_TYPE_EDP) { - if (link->skip_implict_edp_power_control) - return; - if (hws) + if (!link->skip_implict_edp_power_control && hws) hws->funcs.edp_backlight_control(link, false); link->dc->hwss.set_abm_immediate_disable(pipe_ctx); } @@ -1794,9 +1792,7 @@ enum dc_status dce110_apply_single_controller_ctx_to_hw( dc->link_srv->set_dsc_enable(pipe_ctx, true); } - if (!stream->dpms_off && - !(link->connector_signal == SIGNAL_TYPE_EDP && - link->skip_implict_edp_power_control)) + if (!stream->dpms_off) dc->link_srv->set_dpms_on(context, pipe_ctx); /* DCN3.1 FPGA Workaround @@ -1815,9 +1811,7 @@ enum dc_status dce110_apply_single_controller_ctx_to_hw( * is constructed with the same sink). Make sure not to override * and link programming on the main. */ - if (dc_state_get_pipe_subvp_type(context, pipe_ctx) != SUBVP_PHANTOM && - !(link->connector_signal == SIGNAL_TYPE_EDP && - link->skip_implict_edp_power_control)) { + if (dc_state_get_pipe_subvp_type(context, pipe_ctx) != SUBVP_PHANTOM) { pipe_ctx->stream->link->psr_settings.psr_feature_enabled = false; pipe_ctx->stream->link->replay_settings.replay_feature_enabled = false; } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c index 6f1ad651ed2c..b6f3c0480ab6 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c @@ -537,11 +537,9 @@ static void dcn31_reset_back_end_for_pipe( } ASSERT(!pipe_ctx->top_pipe); - link = pipe_ctx->stream->link; + dc->hwss.set_abm_immediate_disable(pipe_ctx); - if (!(link->connector_signal == SIGNAL_TYPE_EDP && - link->skip_implict_edp_power_control)) - dc->hwss.set_abm_immediate_disable(pipe_ctx); + link = pipe_ctx->stream->link; if (dc->hwseq) dc->hwseq->wa_state.skip_blank_stream = false; @@ -557,11 +555,9 @@ static void dcn31_reset_back_end_for_pipe( pipe_ctx->stream_res.tg, OPTC_DSC_DISABLED, 0, 0); - if (!(link->connector_signal == SIGNAL_TYPE_EDP && - link->skip_implict_edp_power_control)) { - pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); - pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); - } + pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); + + pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); if (pipe_ctx->stream_res.tg->funcs->set_odm_bypass) pipe_ctx->stream_res.tg->funcs->set_odm_bypass( pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing); @@ -590,12 +586,7 @@ static void dcn31_reset_back_end_for_pipe( * screen only, the dpms_off would be true but * VBIOS lit up eDP, so check link status too. */ - if (link->connector_signal == SIGNAL_TYPE_EDP && - link->skip_implict_edp_power_control) { - /* DMSS is holding the panel across the commit; skip dpms-off. */ - if (pipe_ctx->stream_res.audio) - dc->hwss.disable_audio_stream(pipe_ctx); - } else if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) + if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) dc->link_srv->set_dpms_off(pipe_ctx); else if (pipe_ctx->stream_res.audio) dc->hwss.disable_audio_stream(pipe_ctx); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c index 4ed7480d1efa..f14c39a643da 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c @@ -1818,11 +1818,8 @@ void dcn35_disable_link_output(struct dc_link *link, disable_link_output_symclk_on_tx_off(link, DP_UNKNOWN_ENCODING); link->phy_state.symclk_state = SYMCLK_ON_TX_OFF; } else { - if (!(signal == SIGNAL_TYPE_EDP && - link->skip_implict_edp_power_control)) { - link_hwss->disable_link_output(link, link_res, signal); - link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; - } + link_hwss->disable_link_output(link, link_res, signal); + link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; } /* * Add the logic to extract BOTH power up and power down sequences diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c index 797d6fb9328d..fa9a5d8065d3 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c @@ -1102,11 +1102,8 @@ void dcn401_disable_link_output(struct dc_link *link, disable_link_output_symclk_on_tx_off(link, DP_UNKNOWN_ENCODING); link->phy_state.symclk_state = SYMCLK_ON_TX_OFF; } else { - if (!(signal == SIGNAL_TYPE_EDP && - link->skip_implict_edp_power_control)) { - link_hwss->disable_link_output(link, link_res, signal); - link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; - } + link_hwss->disable_link_output(link, link_res, signal); + link->phy_state.symclk_state = SYMCLK_OFF_TX_OFF; } if (signal == SIGNAL_TYPE_EDP && @@ -2154,12 +2151,7 @@ void dcn401_reset_back_end_for_pipe( * screen only, the dpms_off would be true but * VBIOS lit up eDP, so check link status too. */ - if (link->connector_signal == SIGNAL_TYPE_EDP && - link->skip_implict_edp_power_control) { - /* DMSS is holding the panel across the commit; skip dpms-off. */ - if (pipe_ctx->stream_res.audio) - dc->hwss.disable_audio_stream(pipe_ctx); - } else if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) + if (!pipe_ctx->stream->dpms_off || link->link_status.link_active) dc->link_srv->set_dpms_off(pipe_ctx); else if (pipe_ctx->stream_res.audio) dc->hwss.disable_audio_stream(pipe_ctx); @@ -2184,15 +2176,12 @@ void dcn401_reset_back_end_for_pipe( * parent pipe. */ if (pipe_ctx->top_pipe == NULL) { - if (!(link->connector_signal == SIGNAL_TYPE_EDP && - link->skip_implict_edp_power_control)) { - dc->hwss.set_abm_immediate_disable(pipe_ctx); + dc->hwss.set_abm_immediate_disable(pipe_ctx); - pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); + pipe_ctx->stream_res.tg->funcs->disable_crtc(pipe_ctx->stream_res.tg); - pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); - } + pipe_ctx->stream_res.tg->funcs->enable_optc_clock(pipe_ctx->stream_res.tg, false); if (pipe_ctx->stream_res.tg->funcs->set_odm_bypass) pipe_ctx->stream_res.tg->funcs->set_odm_bypass( pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing); diff --git a/drivers/gpu/drm/amd/display/modules/power/power_psr.c b/drivers/gpu/drm/amd/display/modules/power/power_psr.c index 0ad4c4924696..5ecb570c204e 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_psr.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_psr.c @@ -58,13 +58,6 @@ bool mod_power_psr_notify_mode_change(struct mod_power *mod_power, // stream_index is passed as validated parameter active_psr_events = core_power->map[stream_index].psr_events; - /* DMSS holds the panel in a forced PSR freeze (e.g. during HDR/SDR toggle). - * Re-running edp_setup_psr would reprogram DPCD 0x170 and disturb the freeze, - * so skip the PSR re-setup until DMSS releases the override. - */ - if (active_psr_events & psr_event_os_override_hold) - return false; - /* Calculate PSR configurations */ mod_power_calc_psr_configs(&psr_config, link, stream); diff --git a/drivers/gpu/drm/amd/display/modules/power/power_replay.c b/drivers/gpu/drm/amd/display/modules/power/power_replay.c index 1ad2ee01d560..e782501442c4 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_replay.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_replay.c @@ -805,13 +805,6 @@ void mod_power_replay_notify_mode_change(struct mod_power *mod_power, core_power = MOD_POWER_TO_CORE(mod_power); active_replay_events = core_power->map[stream_index].replay_events; - /* DMSS holds the panel in a forced freeze (e.g. during HDR/SDR toggle). - * Re-running dp_setup_replay would reprogram DPCD 0x37B and disturb the - * freeze, so skip the replay re-setup until DMSS releases the override. - */ - if (active_replay_events & replay_event_os_override_hold) - return; - link->replay_settings.replay_smu_opt_enable = (link->replay_settings.config.replay_smu_opt_supported && mod_power_only_edp(dc->current_state, stream)); -- cgit v1.2.3 From 7ae85e64d9e0e647247a732068251afaaa0d0595 Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Wed, 17 Jun 2026 17:40:53 -0400 Subject: drm/amd/display: Introduce dc_probe public object model [Why] Performance measurement needs to commit through the same update entry point as planes and streams, with DM working against an abstraction of what to measure rather than the hardware block that performs it. [How] Add dc_probe.h defining dc_probe_type (the measurable quantity), dc_probe_target_state (not measuring / measuring / measured), dc_probe_scope (global only, per-stream/plane deferred), and dc_probe_state (an inline descriptor with copy semantics, no refcount). dc_state carries the committed set as probes[MAX_PROBES] plus probe_count. dc_state_update carries dc_probe_updates as the desired absolute set that DC diffs against the committed set. Reviewed-by: Dominik Kaszewski Signed-off-by: Wenjing Liu Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc_state.c | 3 + drivers/gpu/drm/amd/display/dc/dc.h | 9 +++ drivers/gpu/drm/amd/display/dc/dc_probe.h | 97 +++++++++++++++++++++++++ drivers/gpu/drm/amd/display/dc/inc/core_types.h | 12 +++ 4 files changed, 121 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/dc/dc_probe.h diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_state.c b/drivers/gpu/drm/amd/display/dc/core/dc_state.c index 7edeed52cc32..673fd211014f 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_state.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_state.c @@ -359,6 +359,9 @@ void dc_state_destruct(struct dc_state *state) } state->phantom_plane_count = 0; + memset(state->probes, 0, sizeof(state->probes)); + state->probe_count = 0; + state->stream_mask = 0; memset(&state->res_ctx, 0, sizeof(state->res_ctx)); memset(&state->pp_display_cfg, 0, sizeof(state->pp_display_cfg)); diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 436277acd034..3e22c1f8d4db 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -29,6 +29,7 @@ #include "dc_types.h" #include "dc_state.h" #include "dc_plane.h" +#include "dc_probe.h" #include "grph_object_defs.h" #include "logger_types.h" #include "hdcp_msg_types.h" @@ -2105,6 +2106,14 @@ struct dc_surface_update { struct cm_hist_control *cm_hist_control; }; +struct dc_state_update { + struct dc_stream_state *stream; + struct dc_stream_update *stream_update; + struct dc_surface_update *surface_updates; + int surface_count; + const struct dc_probe_updates *probe_updates; +}; + struct dc_underflow_debug_data { struct dcn_hubbub_reg_state *hubbub_reg_state; struct dcn_hubp_reg_state *hubp_reg_state[MAX_PIPES]; diff --git a/drivers/gpu/drm/amd/display/dc/dc_probe.h b/drivers/gpu/drm/amd/display/dc/dc_probe.h new file mode 100644 index 000000000000..ebf33b162b63 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/dc_probe.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +// +// Copyright 2025 Advanced Micro Devices, Inc. + +#ifndef _DC_PROBE_H_ +#define _DC_PROBE_H_ + +#include "os_types.h" + +/** + * enum dc_probe_type - What DM wants to probe. + * + * Each value names a measurable quantity as an abstraction. DC resolves it to + * whatever HW measurement block fulfills it. DM never selects the HW block. + */ +enum dc_probe_type { + DC_PROBE_PEAK_MEM_BW = 0, + DC_PROBE_AVG_MEM_BW, + DC_PROBE_MEM_LATENCY, + DC_PROBE_URGENT_RAMP_LATENCY, + DC_PROBE_URGENT_ASSERTION_COUNT, + DC_PROBE_PREFETCH_DATA_SIZE, +}; + +/** + * enum dc_probe_target_state - Target lifecycle state DM wants DC to reach. + * + * DM sets this to describe the final state DC must reach by the end of the + * commit. DC performs whatever HW transition sequence is needed. + * + * @DC_PROBE_NOT_MEASURING: probe inactive, no valid data available. + * @DC_PROBE_MEASURING: probe runs continuously. The latest value can be + * read back at any time and may differ on each read. + * @DC_PROBE_MEASURED: probe performed one shot. The result is latched and + * stays valid until DM transitions back to DC_PROBE_NOT_MEASURING. + */ +enum dc_probe_target_state { + DC_PROBE_NOT_MEASURING = 0, + DC_PROBE_MEASURING, + DC_PROBE_MEASURED, +}; + +/** + * enum dc_probe_scope_type - What the probe is scoped to. + * @DC_PROBE_SCOPE_GLOBAL: whole memory subsystem, no stream/plane selector. + * + * Only GLOBAL is implemented. Per-stream/plane scoping must select targets by + * stable id, not object pointer — dc_state copy semantics would dangle a raw + * pointer when the absolute-set commit removes or replaces the target. + */ +enum dc_probe_scope_type { + DC_PROBE_SCOPE_GLOBAL = 0, +}; + +/** + * struct dc_probe_scope - Selects what a probe measures against. + * @type: scope kind, only DC_PROBE_SCOPE_GLOBAL is implemented. + */ +struct dc_probe_scope { + enum dc_probe_scope_type type; +}; + +/** + * struct dc_probe_state - DM-authored descriptor of a single probe. + * + * A plain inline value with copy semantics: no allocation, no refcount. DC + * resolves each descriptor to a HW measurement instance and diffs the desired + * set against the committed set to plan the transition. + * + * @type: what to measure. + * @target_state: desired lifecycle state for this probe. + * @scope: what the probe is scoped to (GLOBAL only for now). + */ +struct dc_probe_state { + enum dc_probe_type type; + enum dc_probe_target_state target_state; + struct dc_probe_scope scope; +}; + +#define MAX_PROBES 1 + +/** + * struct dc_probe_updates - Absolute set of probes DM wants active. + * + * Mirrors the plane/stream absolute-set model: the array is the complete + * desired set. DC compares it against the committed set to add, remove, or + * transition probes. + * + * @probes: desired probe descriptors. + * @probe_count: number of valid entries in @probes. + */ +struct dc_probe_updates { + struct dc_probe_state probes[MAX_PROBES]; + int probe_count; +}; + +#endif /* _DC_PROBE_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/inc/core_types.h b/drivers/gpu/drm/amd/display/dc/inc/core_types.h index cbbc1fb4b3dd..c42626101cd7 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/core_types.h +++ b/drivers/gpu/drm/amd/display/dc/inc/core_types.h @@ -622,6 +622,7 @@ struct dc_state { * @stream_status: Planes status on a given stream */ struct dc_stream_status stream_status[MAX_PIPES]; + /** * @phantom_streams: Stream state properties for phantoms */ @@ -645,6 +646,17 @@ struct dc_state { * @stream_count: Total phantom planes in use */ uint8_t phantom_plane_count; + + /** + * @probes: Committed absolute set of probe descriptors. + */ + struct dc_probe_state probes[MAX_PROBES]; + + /** + * @probe_count: Number of valid entries in @probes. + */ + int probe_count; + /** * @res_ctx: Persistent state of resources */ -- cgit v1.2.3 From a7e804e427fd3d7b6b9e12e65024a795fef6a0e3 Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Wed, 24 Jun 2026 16:20:34 -0400 Subject: drm/amd/display: Introduce dc_update_state unified commit interface [Why] dc_update_planes_and_stream() is typed against surface/stream arrays only, leaving no extensible slot for future object classes. The public entry point names and signatures need to stay stable as new object classes are added. [How] Introduce dc_update_state() and dc_check_state_update(), both taking a dc_state_update root object. dc_update_state() routes stream and plane updates through the existing pipeline. Keep dc_update_planes_and_stream() and dc_check_update_surfaces_for_stream() as shims that delegate. Change dc_update_state_init() to take a const dc_state_update* and rename dc_update_planes_and_stream_{init,prepare,execute,cleanup} -> dc_update_state_{init,prepare,execute,cleanup}. Rename surface_update_type -> dc_update_type and surface_update_descriptor -> dc_update_descriptor. Reviewed-by: Dominik Kaszewski Signed-off-by: Wenjing Liu Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 188 +++++++++++++++++++---------- drivers/gpu/drm/amd/display/dc/dc.h | 19 ++- drivers/gpu/drm/amd/display/dc/dc_stream.h | 21 ++-- 3 files changed, 155 insertions(+), 73 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 0802837d9e3f..086fed931421 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -151,8 +151,8 @@ static const char DC_BUILD_ID[] = "production-build"; /* Private functions */ static inline void elevate_update_type( - struct surface_update_descriptor *descriptor, - enum surface_update_type new_type, + struct dc_update_descriptor *descriptor, + enum dc_update_type new_type, enum dc_lock_descriptor new_locks ) { @@ -2792,10 +2792,10 @@ static bool is_surface_in_context( return false; } -static struct surface_update_descriptor get_plane_info_update_type(const struct dc_surface_update *u) +static struct dc_update_descriptor get_plane_info_update_type(const struct dc_surface_update *u) { struct pipe_update_bits *update_bits = &u->surface->update_bits; - struct surface_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; + struct dc_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; if (!u->plane_info) return update_type; @@ -2882,12 +2882,12 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct return update_type; } -static struct surface_update_descriptor get_scaling_info_update_type( +static struct dc_update_descriptor get_scaling_info_update_type( const struct dc_check_config *check_config, const struct dc_surface_update *u) { struct pipe_update_bits *update_bits = &u->surface->update_bits; - struct surface_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; + struct dc_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; if (!u->scaling_info) return update_type; @@ -2938,11 +2938,11 @@ static struct surface_update_descriptor get_scaling_info_update_type( return update_type; } -static struct surface_update_descriptor det_surface_update( +static struct dc_update_descriptor det_surface_update( const struct dc_check_config *check_config, struct dc_surface_update *u) { - struct surface_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; + struct dc_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; struct pipe_update_bits *update_bits = &u->surface->update_bits; if (u->surface->force_full_update) { @@ -2953,8 +2953,7 @@ static struct surface_update_descriptor det_surface_update( dc_pipe_update_bits_clear(update_bits); - struct surface_update_descriptor inner_type = get_plane_info_update_type(u); - + struct dc_update_descriptor inner_type = get_plane_info_update_type(u); elevate_update_type(&overall_type, inner_type.update_type, inner_type.lock_descriptor); inner_type = get_scaling_info_update_type(check_config, u); @@ -3096,13 +3095,13 @@ static void force_immediate_gsl_plane_flip(struct dc *dc, struct dc_surface_upda } } -static struct surface_update_descriptor check_update_surfaces_for_stream( +static struct dc_update_descriptor check_update_surfaces_for_stream( const struct dc_check_config *check_config, struct dc_surface_update *updates, int surface_count, struct dc_stream_update *stream_update) { - struct surface_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; + struct dc_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; /* When countdown finishes, promote this flip to full to trigger deferred final transition */ if (check_config->deferred_transition_state && !check_config->transition_countdown_to_steady_state) { @@ -3225,7 +3224,7 @@ static struct surface_update_descriptor check_update_surfaces_for_stream( } for (int i = 0 ; i < surface_count; i++) { - struct surface_update_descriptor inner_type = + struct dc_update_descriptor inner_type = det_surface_update(check_config, &updates[i]); elevate_update_type(&overall_type, inner_type.update_type, inner_type.lock_descriptor); @@ -3237,20 +3236,57 @@ static struct surface_update_descriptor check_update_surfaces_for_stream( /* * dc_check_update_surfaces_for_stream() - Determine update type (fast, med, or full) * - * See :c:type:`enum surface_update_type ` for explanation of update types + * See :c:type:`enum dc_update_type ` for explanation of update types + */ +/** + * dc_check_state_update - Classify a dc_state_update by locking / re-entrancy requirements. + * @check_config: ASIC capabilities and display configuration context + * @updates: root update object describing the full desired commit + * + * Determines whether the update requires a fast, medium, or full lock + * by inspecting the stream, stream_update, and surface_updates carried on + * the root object. Perfmon classification is reserved for a future slice. + * + * Return: dc_update_descriptor with update_type and lock_descriptor. */ -struct surface_update_descriptor dc_check_update_surfaces_for_stream( +struct dc_update_descriptor dc_check_state_update( + const struct dc_check_config *check_config, + struct dc_state_update *updates) +{ + if (updates->stream_update) + stream_update_flags_clear(&updates->stream_update->stream->update_flags); + for (int i = 0; i < updates->surface_count; i++) + dc_pipe_update_bits_clear(&updates->surface_updates[i].surface->update_bits); + + return check_update_surfaces_for_stream(check_config, updates->surface_updates, + updates->surface_count, updates->stream_update); +} + +/** + * dc_check_update_surfaces_for_stream - Shim for dc_check_state_update. + * @check_config: ASIC capabilities and display configuration context + * @updates: array of surface update descriptors + * @surface_count: number of entries in @updates + * @stream_update: optional stream update + * + * Packs the individual arguments into a dc_state_update and forwards to + * dc_check_state_update(). Preserved for out-of-tree and incremental callers. + * + * Return: dc_update_descriptor with update_type and lock_descriptor. + */ +struct dc_update_descriptor dc_check_update_surfaces_for_stream( const struct dc_check_config *check_config, struct dc_surface_update *updates, int surface_count, struct dc_stream_update *stream_update) { - if (stream_update) - stream_update_flags_clear(&stream_update->stream->update_flags); - for (int i = 0; i < surface_count; i++) - dc_pipe_update_bits_clear(&updates[i].surface->update_bits); + struct dc_state_update root = { + .surface_updates = updates, + .surface_count = surface_count, + .stream_update = stream_update, + }; - return check_update_surfaces_for_stream(check_config, updates, surface_count, stream_update); + return dc_check_state_update(check_config, &root); } static struct dc_stream_status *stream_get_status( @@ -3268,7 +3304,7 @@ static struct dc_stream_status *stream_get_status( return NULL; } -static const enum surface_update_type update_surface_trace_level = UPDATE_TYPE_FULL; +static const enum dc_update_type update_surface_trace_level = UPDATE_TYPE_FULL; static void copy_surface_update_to_plane( struct dc_plane_state *surface, @@ -3736,13 +3772,13 @@ static bool update_planes_and_stream_state(struct dc *dc, struct dc_surface_update *srf_updates, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type *new_update_type, + enum dc_update_type *new_update_type, struct dc_state **new_context) { struct dc_state *context; int i; unsigned int j; - enum surface_update_type update_type; + enum dc_update_type update_type; const struct dc_stream_status *stream_status; struct dc_context *dc_ctx = dc->ctx; @@ -4151,7 +4187,7 @@ static void add_link_update_dsc_config_sequence( static void commit_planes_do_stream_update_sequence(struct dc *dc, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type, + enum dc_update_type update_type, struct dc_state *context, struct block_sequence block_sequence[MAX_HWSS_BLOCK_SEQUENCE_SIZE], unsigned int *num_steps) @@ -4322,7 +4358,7 @@ static void commit_planes_do_stream_update_sequence(struct dc *dc, static void commit_planes_do_stream_update(struct dc *dc, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type, + enum dc_update_type update_type, struct dc_state *context) { unsigned int j; @@ -4725,7 +4761,7 @@ static void commit_planes_for_stream_fast(struct dc *dc, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type, + enum dc_update_type update_type, struct dc_state *context) { int i; @@ -4845,7 +4881,7 @@ static void commit_planes_for_stream(struct dc *dc, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type, + enum dc_update_type update_type, struct dc_state *context) { int i; @@ -5928,7 +5964,7 @@ static bool update_planes_and_stream_v2(struct dc *dc, struct dc_stream_update *stream_update) { struct dc_state *context; - enum surface_update_type update_type; + enum dc_update_type update_type; struct dc_fast_update fast_update[MAX_SURFACES] = {0}; /* In cases where MPO and split or ODM are used transitions can @@ -6013,7 +6049,7 @@ static void commit_planes_and_stream_update_on_current_context(struct dc *dc, struct dc_surface_update *srf_updates, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type) + enum dc_update_type update_type) { struct dc_fast_update fast_update[MAX_SURFACES] = {0}; @@ -6045,7 +6081,7 @@ static void commit_planes_and_stream_update_with_new_context(struct dc *dc, struct dc_surface_update *srf_updates, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update, - enum surface_update_type update_type, + enum dc_update_type update_type, struct dc_state *new_context) { bool skip_new_context = false; @@ -6113,7 +6149,7 @@ static bool update_planes_and_stream_v3(struct dc *dc, struct dc_stream_update *stream_update) { struct dc_state *new_context; - enum surface_update_type update_type; + enum dc_update_type update_type; /* * When this function returns true and new_context is not equal to @@ -6159,28 +6195,61 @@ static void clear_update_bits(struct dc_surface_update *srf_updates, dc_pipe_update_bits_clear(&srf_updates[i].surface->update_bits); } +/** + * dc_update_state - Commit an absolute dc_state_update. + * @dc: DC structure + * @updates: root update object carrying stream, plane, and probe updates + * + * When stream is non-NULL the stream and its plane updates are committed via + * the init/prepare/execute/cleanup pipeline. Probe commit is reserved for a + * future slice. dc_update_planes_and_stream() is now a shim over this function. + * + * Return: true on success, false on failure. + */ +bool dc_update_state(struct dc *dc, struct dc_state_update *updates) +{ + if (updates->stream != NULL) { + struct dc_update_scratch_space *scratch = dc_update_state_init(dc, updates); + bool more = true; + + while (more) { + if (!dc_update_state_prepare(scratch)) + return false; + + dc_update_state_execute(scratch); + more = dc_update_state_cleanup(scratch); + } + } + + return true; +} + +/** + * dc_update_planes_and_stream - Shim for dc_update_state. + * @dc: DC structure + * @srf_updates: array of surface update descriptors + * @surface_count: number of entries in @srf_updates + * @stream: target stream + * @stream_update: optional stream update + * + * Packs the individual arguments into a dc_state_update and forwards to + * dc_update_state(). Preserved for out-of-tree and incremental callers. + * + * Return: true on success; false on failure. + */ bool dc_update_planes_and_stream(struct dc *dc, struct dc_surface_update *srf_updates, int surface_count, struct dc_stream_state *stream, struct dc_stream_update *stream_update) { - struct dc_update_scratch_space *scratch = dc_update_planes_and_stream_init( - dc, - srf_updates, - surface_count, - stream, - stream_update - ); - bool more = true; - - while (more) { - if (!dc_update_planes_and_stream_prepare(scratch)) - return false; + struct dc_state_update updates = { + .stream = stream, + .stream_update = stream_update, + .surface_updates = srf_updates, + .surface_count = surface_count, + }; - dc_update_planes_and_stream_execute(scratch); - more = dc_update_planes_and_stream_cleanup(scratch); - } - return true; + return dc_update_state(dc, &updates); } void dc_commit_updates_for_stream(struct dc *dc, @@ -8113,7 +8182,7 @@ struct dc_update_scratch_space { struct dc_stream_update *stream_update; bool update_v3; bool do_clear_update_bits; - enum surface_update_type update_type; + enum dc_update_type update_type; struct dc_state *new_context; enum update_v3_flow flow; struct dc_state *backup_context; @@ -8416,23 +8485,20 @@ static bool update_planes_and_stream_cleanup_v3( return false; } -struct dc_update_scratch_space *dc_update_planes_and_stream_init( +struct dc_update_scratch_space *dc_update_state_init( struct dc *dc, - struct dc_surface_update *surface_updates, - int surface_count, - struct dc_stream_state *stream, - struct dc_stream_update *stream_update + const struct dc_state_update *updates ) { const enum dce_version version = dc->ctx->dce_version; - struct dc_update_scratch_space *scratch = stream->update_scratch; + struct dc_update_scratch_space *scratch = updates->stream->update_scratch; *scratch = (struct dc_update_scratch_space){ .dc = dc, - .surface_updates = surface_updates, - .surface_count = surface_count, - .stream = stream, - .stream_update = stream_update, + .surface_updates = updates->surface_updates, + .surface_count = updates->surface_count, + .stream = updates->stream, + .stream_update = updates->stream_update, .update_v3 = version >= DCN_VERSION_4_01 || version == DCN_VERSION_3_2 || version == DCN_VERSION_3_21, .do_clear_update_bits = version >= DCN_VERSION_1_0, }; @@ -8440,7 +8506,7 @@ struct dc_update_scratch_space *dc_update_planes_and_stream_init( return scratch; } -bool dc_update_planes_and_stream_prepare( +bool dc_update_state_prepare( struct dc_update_scratch_space *scratch ) { @@ -8449,7 +8515,7 @@ bool dc_update_planes_and_stream_prepare( : update_planes_and_stream_prepare_v2(scratch); } -void dc_update_planes_and_stream_execute( +void dc_update_state_execute( const struct dc_update_scratch_space *scratch ) { @@ -8458,7 +8524,7 @@ void dc_update_planes_and_stream_execute( : update_planes_and_stream_execute_v2(scratch); } -bool dc_update_planes_and_stream_cleanup( +bool dc_update_state_cleanup( struct dc_update_scratch_space *scratch ) { diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 3e22c1f8d4db..f06539df7f0a 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -519,7 +519,7 @@ struct dc_static_screen_params { * underscan we don't expect to see this call at all. */ -enum surface_update_type { +enum dc_update_type { UPDATE_TYPE_FAST, /* super fast, safe to execute in isr */ UPDATE_TYPE_MED, /* ISR safe, most of programming needed, no bw/clk change*/ UPDATE_TYPE_FULL, /* may need to shuffle resources */ @@ -532,8 +532,8 @@ enum dc_lock_descriptor { LOCK_DESCRIPTOR_GLOBAL = 0x4, }; -struct surface_update_descriptor { - enum surface_update_type update_type; +struct dc_update_descriptor { + enum dc_update_type update_type; enum dc_lock_descriptor lock_descriptor; }; @@ -2114,6 +2114,19 @@ struct dc_state_update { const struct dc_probe_updates *probe_updates; }; +/** + * dc_update_state - Commit an absolute dc_state_update. + * @dc: DC structure + * @updates: root update object carrying stream, plane, and probe updates + * + * When stream is non-NULL the stream and its plane updates are committed via + * the init/prepare/execute/cleanup pipeline. Probe commit is reserved for a + * future slice. dc_update_planes_and_stream() is now a shim over this function. + * + * Return: true on success, false on failure. + */ +bool dc_update_state(struct dc *dc, struct dc_state_update *updates); + struct dc_underflow_debug_data { struct dcn_hubbub_reg_state *hubbub_reg_state; struct dcn_hubp_reg_state *hubp_reg_state[MAX_PIPES]; diff --git a/drivers/gpu/drm/amd/display/dc/dc_stream.h b/drivers/gpu/drm/amd/display/dc/dc_stream.h index 8b164edc9c51..a866688ad9db 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_stream.h +++ b/drivers/gpu/drm/amd/display/dc/dc_stream.h @@ -409,29 +409,27 @@ bool dc_update_planes_and_stream(struct dc *dc, struct dc_stream_update *stream_update); struct dc_update_scratch_space; +struct dc_state_update; size_t dc_update_scratch_space_size(void); -struct dc_update_scratch_space *dc_update_planes_and_stream_init( +struct dc_update_scratch_space *dc_update_state_init( struct dc *dc, - struct dc_surface_update *surface_updates, - int surface_count, - struct dc_stream_state *dc_stream, - struct dc_stream_update *stream_update + const struct dc_state_update *updates ); // Locked, false is failed -bool dc_update_planes_and_stream_prepare( +bool dc_update_state_prepare( struct dc_update_scratch_space *scratch ); // Unlocked -void dc_update_planes_and_stream_execute( +void dc_update_state_execute( const struct dc_update_scratch_space *scratch ); // Locked, true if call again -bool dc_update_planes_and_stream_cleanup( +bool dc_update_state_cleanup( struct dc_update_scratch_space *scratch ); @@ -518,7 +516,12 @@ void dc_enable_stereo( /* Triggers multi-stream synchronization. */ void dc_trigger_sync(struct dc *dc, struct dc_state *context); -struct surface_update_descriptor dc_check_update_surfaces_for_stream( +struct dc_update_descriptor dc_check_state_update( + const struct dc_check_config *check_config, + struct dc_state_update *updates); + +/* Shim: packs args into dc_state_update and calls dc_check_state_update(). */ +struct dc_update_descriptor dc_check_update_surfaces_for_stream( const struct dc_check_config *check_config, struct dc_surface_update *updates, int surface_count, -- cgit v1.2.3 From 2ab5a13778265fe082050699ca0e921029a653f9 Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Wed, 17 Jun 2026 10:30:29 -0400 Subject: drm/amd/display: Refactor dc_validation_set array into single root struct [Why] dc_validate_with_context() took a C array of per-stream elements plus a separate count. This flat array cannot be extended to carry global objects without widening the signature further. [How] Rename the per-stream element to dc_validation_stream, and make dc_validation_set a root struct holding streams[MAX_STREAMS] and stream_count. Update dc_validate_with_context() to take a single const dc_validation_set * and propagate the new shape through all DC-layer callers and helpers. Remove the never-implemented dce112_validate_with_context declaration and the orphaned dce100 forward-declare. No behavior change. Reviewed-by: Dominik Kaszewski Signed-off-by: Wenjing Liu Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 11 ++-- drivers/gpu/drm/amd/display/dc/core/dc_resource.c | 60 ++++++++++------------ drivers/gpu/drm/amd/display/dc/dc.h | 22 ++++++-- drivers/gpu/drm/amd/display/dc/inc/resource.h | 3 +- .../display/dc/resource/dce100/dce100_resource.h | 1 - .../display/dc/resource/dce112/dce112_resource.h | 7 --- 6 files changed, 52 insertions(+), 52 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 086fed931421..f4c5d5b14072 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -2479,7 +2479,7 @@ enum dc_status dc_commit_streams(struct dc *dc, struct dc_commit_streams_params unsigned int i, j; struct dc_state *context; enum dc_status res = DC_OK; - struct dc_validation_set set[MAX_STREAMS] = {0}; + struct dc_validation_set set = {0}; struct pipe_ctx *pipe; bool handle_exit_odm2to1 = false; @@ -2512,14 +2512,15 @@ enum dc_status dc_commit_streams(struct dc *dc, struct dc_commit_streams_params dc_stream_log(dc, stream); - set[i].stream = stream; + set.streams[i].stream = stream; if (status) { - set[i].plane_count = (uint8_t)status->plane_count; + set.streams[i].plane_count = (uint8_t)status->plane_count; for (j = 0; j < (unsigned int)status->plane_count; j++) - set[i].plane_states[j] = status->plane_states[j]; + set.streams[i].plane_states[j] = status->plane_states[j]; } } + set.stream_count = (uint8_t)params->stream_count; /* ODM Combine 2:1 power optimization is only applied for single stream * scenario, it uses extra pipes than needed to reduce power consumption @@ -2543,7 +2544,7 @@ enum dc_status dc_commit_streams(struct dc *dc, struct dc_commit_streams_params context->power_source = params->power_source; - res = dc_validate_with_context(dc, set, params->stream_count, context, DC_VALIDATE_MODE_AND_PROGRAMMING); + res = dc_validate_with_context(dc, &set, context, DC_VALIDATE_MODE_AND_PROGRAMMING); /* * Only update link encoder to stream assignment after bandwidth validation passed. diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c index b21d41df0fab..d9492a460c2a 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c @@ -4325,8 +4325,7 @@ bool dc_resource_is_dsc_encoding_supported(const struct dc *dc) static bool planes_changed_for_existing_stream(struct dc_state *context, struct dc_stream_state *stream, - const struct dc_validation_set set[], - unsigned int set_count) + const struct dc_validation_set *set) { unsigned int i, j; struct dc_stream_status *stream_status = NULL; @@ -4343,18 +4342,18 @@ static bool planes_changed_for_existing_stream(struct dc_state *context, return false; } - for (i = 0; i < set_count; i++) - if (set[i].stream == stream) + for (i = 0; i < set->stream_count; i++) + if (set->streams[i].stream == stream) break; - if (i == set_count) + if (i == set->stream_count) ASSERT(0); - if (set[i].plane_count != stream_status->plane_count) + if (set->streams[i].plane_count != stream_status->plane_count) return true; - for (j = 0; j < set[i].plane_count; j++) - if (set[i].plane_states[j] != stream_status->plane_states[j]) + for (j = 0; j < set->streams[i].plane_count; j++) + if (set->streams[i].plane_states[j] != stream_status->plane_states[j]) return true; return false; @@ -4363,23 +4362,22 @@ static bool planes_changed_for_existing_stream(struct dc_state *context, static bool add_all_planes_for_stream( const struct dc *dc, struct dc_stream_state *stream, - const struct dc_validation_set set[], - unsigned int set_count, + const struct dc_validation_set *set, struct dc_state *state) { unsigned int i, j; - for (i = 0; i < set_count; i++) - if (set[i].stream == stream) + for (i = 0; i < set->stream_count; i++) + if (set->streams[i].stream == stream) break; - if (i == set_count) { + if (i == set->stream_count) { dm_error("Stream %p not found in set!\n", stream); return false; } - for (j = 0; j < set[i].plane_count; j++) - if (!dc_state_add_plane(dc, stream, set[i].plane_states[j], state)) + for (j = 0; j < set->streams[i].plane_count; j++) + if (!dc_state_add_plane(dc, stream, set->streams[i].plane_states[j], state)) return false; return true; @@ -4389,8 +4387,7 @@ static bool add_all_planes_for_stream( * dc_validate_with_context - Validate and update the potential new stream in the context object * * @dc: Used to get the current state status - * @set: An array of dc_validation_set with all the current streams reference - * @set_count: Total of streams + * @set: Root validation object holding all streams and their planes * @context: New context * @validate_mode: identify the validation mode * @@ -4404,8 +4401,7 @@ static bool add_all_planes_for_stream( * In case of success, return DC_OK (1), otherwise, return a DC error. */ enum dc_status dc_validate_with_context(struct dc *dc, - const struct dc_validation_set set[], - unsigned int set_count, + const struct dc_validation_set *set, struct dc_state *context, enum dc_validate_mode validate_mode) { @@ -4426,8 +4422,8 @@ enum dc_status dc_validate_with_context(struct dc *dc, for (i = 0; i < old_stream_count; i++) { struct dc_stream_state *stream = context->streams[i]; - for (j = 0; j < set_count; j++) { - if (stream == set[j].stream) { + for (j = 0; j < set->stream_count; j++) { + if (stream == set->streams[j].stream) { found = true; break; } @@ -4440,8 +4436,8 @@ enum dc_status dc_validate_with_context(struct dc *dc, } /* Second, build a list of new streams */ - for (i = 0; i < set_count; i++) { - struct dc_stream_state *stream = set[i].stream; + for (i = 0; i < set->stream_count; i++) { + struct dc_stream_state *stream = set->streams[i].stream; for (j = 0; j < old_stream_count; j++) { if (stream == context->streams[j]) { @@ -4459,10 +4455,10 @@ enum dc_status dc_validate_with_context(struct dc *dc, /* Build a list of unchanged streams which is necessary for handling * planes change such as added, removed, and updated. */ - for (i = 0; i < set_count; i++) { + for (i = 0; i < set->stream_count; i++) { /* Check if stream is part of the delete list */ for (j = 0; j < del_streams_count; j++) { - if (set[i].stream == del_streams[j]) { + if (set->streams[i].stream == del_streams[j]) { found = true; break; } @@ -4471,7 +4467,7 @@ enum dc_status dc_validate_with_context(struct dc *dc, if (!found) { /* Check if stream is part of the add list */ for (j = 0; j < add_streams_count; j++) { - if (set[i].stream == add_streams[j]) { + if (set->streams[i].stream == add_streams[j]) { found = true; break; } @@ -4479,7 +4475,7 @@ enum dc_status dc_validate_with_context(struct dc *dc, } if (!found) - unchanged_streams[unchanged_streams_count++] = set[i].stream; + unchanged_streams[unchanged_streams_count++] = set->streams[i].stream; found = false; } @@ -4488,8 +4484,7 @@ enum dc_status dc_validate_with_context(struct dc *dc, for (i = 0; i < unchanged_streams_count; i++) { if (planes_changed_for_existing_stream(context, unchanged_streams[i], - set, - set_count)) { + set)) { if (!dc_state_rem_all_planes_for_stream(dc, unchanged_streams[i], @@ -4557,7 +4552,7 @@ enum dc_status dc_validate_with_context(struct dc *dc, if (res != DC_OK) goto fail; - if (!add_all_planes_for_stream(dc, add_streams[i], set, set_count, context)) { + if (!add_all_planes_for_stream(dc, add_streams[i], set, context)) { res = DC_FAIL_ATTACH_SURFACES; goto fail; } @@ -4567,9 +4562,8 @@ enum dc_status dc_validate_with_context(struct dc *dc, for (i = 0; i < unchanged_streams_count; i++) { if (planes_changed_for_existing_stream(context, unchanged_streams[i], - set, - set_count)) { - if (!add_all_planes_for_stream(dc, unchanged_streams[i], set, set_count, context)) { + set)) { + if (!add_all_planes_for_stream(dc, unchanged_streams[i], set, context)) { res = DC_FAIL_ATTACH_SURFACES; goto fail; } diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index f06539df7f0a..bc2ed23407cc 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -2182,9 +2182,9 @@ void dc_post_update_surfaces_to_stream( void dc_get_default_tiling_info(const struct dc *dc, struct dc_tiling_info *tiling_info); /** - * struct dc_validation_set - Struct to store surface/stream associations for validation + * struct dc_validation_stream - Per-stream surface/stream association for validation */ -struct dc_validation_set { +struct dc_validation_stream { /** * @stream: Stream state properties */ @@ -2201,6 +2201,21 @@ struct dc_validation_set { uint8_t plane_count; }; +/** + * struct dc_validation_set - Root validation input grouping all streams for a commit + */ +struct dc_validation_set { + /** + * @streams: Per-stream entries (stream + its planes) + */ + struct dc_validation_stream streams[MAX_STREAMS]; + + /** + * @stream_count: Number of active entries in @streams + */ + uint8_t stream_count; +}; + bool dc_validate_boot_timing(const struct dc *dc, const struct dc_sink *sink, struct dc_crtc_timing *crtc_timing); @@ -2208,8 +2223,7 @@ bool dc_validate_boot_timing(const struct dc *dc, enum dc_status dc_validate_plane(struct dc *dc, const struct dc_plane_state *plane_state); enum dc_status dc_validate_with_context(struct dc *dc, - const struct dc_validation_set set[], - unsigned int set_count, + const struct dc_validation_set *set, struct dc_state *context, enum dc_validate_mode validate_mode); diff --git a/drivers/gpu/drm/amd/display/dc/inc/resource.h b/drivers/gpu/drm/amd/display/dc/inc/resource.h index 945cdcefb7d4..b64ba8c0adb1 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/resource.h +++ b/drivers/gpu/drm/amd/display/dc/inc/resource.h @@ -577,8 +577,7 @@ struct pipe_ctx *resource_find_free_secondary_pipe_legacy( const struct pipe_ctx *primary_pipe); bool resource_validate_attach_surfaces( - const struct dc_validation_set set[], - int set_count, + const struct dc_validation_set *set, const struct dc_state *old_context, struct dc_state *context, const struct resource_pool *pool); diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.h index dd150a4b4610..bc130793348a 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.h @@ -33,7 +33,6 @@ struct dc; struct resource_pool; -struct dc_validation_set; struct resource_pool *dce100_create_resource_pool( uint8_t num_virtual_links, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce112/dce112_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dce112/dce112_resource.h index 3efc4c55d2d2..f2493945b8f4 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce112/dce112_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dce112/dce112_resource.h @@ -35,13 +35,6 @@ struct resource_pool *dce112_create_resource_pool( uint8_t num_virtual_links, struct dc *dc); -enum dc_status dce112_validate_with_context( - struct dc *dc, - const struct dc_validation_set set[], - int set_count, - struct dc_state *context, - struct dc_state *old_context); - enum dc_status dce112_validate_bandwidth( struct dc *dc, struct dc_state *context, -- cgit v1.2.3 From 598abe0aadcf96acae2bb2315dea4f9a1f2b6659 Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Wed, 24 Jun 2026 17:57:17 -0400 Subject: drm/amd/display: Introduce dc_state_get_status unified status accessor [Why] dc_state_get_stream_status() is typed against streams only, leaving no extensible slot for future status classes. The public status accessor signature needs to stay stable as new status classes are added. [How] Add a dc_get_status_type bitmask and the dc_get_status_options / dc_state_status structs. Implement dc_state_get_status() to populate the output object per the options bitmask, and make dc_state_get_stream_status() a shim over it. Reviewed-by: Dominik Kaszewski Signed-off-by: Wenjing Liu Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc_state.c | 57 +++++++++++++++++++------- drivers/gpu/drm/amd/display/dc/dc.h | 44 ++++++++++++++++++++ drivers/gpu/drm/amd/display/dc/dc_state.h | 7 ++++ 3 files changed, 94 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_state.c b/drivers/gpu/drm/amd/display/dc/core/dc_state.c index 673fd211014f..436e88f64f86 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_state.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_state.c @@ -25,6 +25,7 @@ #include "dc_types.h" #include "core_types.h" #include "core_status.h" +#include "dc.h" #include "dc_state.h" #include "dc_state_priv.h" #include "dc_stream_priv.h" @@ -681,28 +682,56 @@ bool dc_state_add_all_planes_for_stream( /* Private dc_state functions */ /** - * dc_state_get_stream_status - Get stream status from given dc state - * @state: DC state to find the stream status in - * @stream: The stream to get the stream status for + * dc_state_get_status - Unified status readback for dc_state. + * @status: output object populated per options->types + * @options: selects source state, status classes to fill, and optional filters * - * The given stream is expected to exist in the given dc state. Otherwise, NULL - * will be returned. + * Return: DC_OK on success, DC_ERROR_UNEXPECTED if options or state is NULL. */ -struct dc_stream_status *dc_state_get_stream_status( - struct dc_state *state, - const struct dc_stream_state *stream) +enum dc_status dc_state_get_status(struct dc_state_status *status, + const struct dc_get_status_options *options) { uint8_t i; - if (state == NULL) - return NULL; + if (!status || !options || !options->state) + return DC_ERROR_UNEXPECTED; - for (i = 0; i < state->stream_count; i++) { - if (stream == state->streams[i]) - return &state->stream_status[i]; + if (options->types & DC_GET_STATUS_STREAM) { + status->stream_count = 0; + for (i = 0; i < options->state->stream_count; i++) { + if (options->stream && + options->stream != options->state->streams[i]) + continue; + status->stream_status[status->stream_count++] = + &options->state->stream_status[i]; + } } - return NULL; + return DC_OK; +} + +/** + * dc_state_get_stream_status - Shim for dc_state_get_status. + * @state: state to search + * @stream: stream to find status for + * + * Return: pointer to the matching dc_stream_status, or NULL if not found. + */ +struct dc_stream_status *dc_state_get_stream_status( + struct dc_state *state, + const struct dc_stream_state *stream) +{ + struct dc_state_status status = {}; + struct dc_get_status_options options = { + .state = state, + .types = DC_GET_STATUS_STREAM, + .stream = stream, + }; + + if (dc_state_get_status(&status, &options) != DC_OK) + return NULL; + + return status.stream_count > 0 ? status.stream_status[0] : NULL; } enum mall_stream_type dc_state_get_pipe_subvp_type(const struct dc_state *state, diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index bc2ed23407cc..2da89c7470de 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -2127,6 +2127,50 @@ struct dc_state_update { */ bool dc_update_state(struct dc *dc, struct dc_state_update *updates); +/** + * enum dc_get_status_type - Bitmask selecting which status classes to populate. + * @DC_GET_STATUS_STREAM: populate stream_status fields in dc_state_status + */ +enum dc_get_status_type { + DC_GET_STATUS_STREAM = (1u << 0), +}; + +/** + * struct dc_get_status_options - Input selector for dc_state_get_status. + * @state: source state to read status from + * @types: OR of dc_get_status_type values selecting classes to populate + * @stream: optional stream filter for DC_GET_STATUS_STREAM. NULL means + * populate status for all streams in the state + */ +struct dc_get_status_options { + struct dc_state *state; + uint32_t types; + const struct dc_stream_state *stream; +}; + +/** + * struct dc_state_status - Output-only status object from dc_state_get_status. + * @stream_count: number of valid entries in stream_status (DC_GET_STATUS_STREAM) + * @stream_status: pointers to live per-stream status entries + */ +struct dc_state_status { + int stream_count; + struct dc_stream_status *stream_status[MAX_STREAMS]; +}; + +/** + * dc_state_get_status - Unified status readback for dc_state. + * @status: output object populated according to options->types + * @options: selects the source state, status classes to fill, and filters + * + * dc_state_get_stream_status() is a thin shim over this function with + * types = DC_GET_STATUS_STREAM and a stream filter. + * + * Return: DC_OK on success, DC_ERROR_UNEXPECTED if state is NULL. + */ +enum dc_status dc_state_get_status(struct dc_state_status *status, + const struct dc_get_status_options *options); + struct dc_underflow_debug_data { struct dcn_hubbub_reg_state *hubbub_reg_state; struct dcn_hubp_reg_state *hubp_reg_state[MAX_PIPES]; diff --git a/drivers/gpu/drm/amd/display/dc/dc_state.h b/drivers/gpu/drm/amd/display/dc/dc_state.h index db1e63a7d460..acf461225e9d 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_state.h +++ b/drivers/gpu/drm/amd/display/dc/dc_state.h @@ -74,4 +74,11 @@ bool dc_state_add_all_planes_for_stream( struct dc_stream_status *dc_state_get_stream_status( struct dc_state *state, const struct dc_stream_state *stream); + +struct dc_state_status; +struct dc_get_status_options; + +enum dc_status dc_state_get_status(struct dc_state_status *status, + const struct dc_get_status_options *options); + #endif /* _DC_STATE_H_ */ -- cgit v1.2.3 From 85df2884384beb20753e96e795f38621e9c465fa Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Thu, 18 Jun 2026 18:05:07 -0400 Subject: drm/amd/display: Introduce program_perfmon hwss hook and BLS perfmon sequence [Why] The hubbub perfmon peak-bandwidth path used a monolithic function combining counter configuration and enable in one call, and wrote results into a caller-supplied struct outside the dc_state model. A BLS-driven approach builds the measurement from stateless primitives, gates it to OTG frame boundaries, and stores results in dc_state. [How] - Rename the peak-BW vtable members from "unbounded_bandwidth" to "out_of_order_bandwidth" and split them into arm and start operations. - Add the full set of perfmon BLS primitives - param structs, union members, enum entries, executors, and hwss_add_* builders - for reset, arm, every start_measuring_* and every get_* operation. - Add dc_probe_status (valid, type, result union) plus probe_status[] to dc_state. - Replace the measure_memory_qos hook with program_perfmon(dc, context), which writes results into probe_status[]. - dc_get_qos_info no longer calls the removed hook. Reviewed-by: Dominik Kaszewski Signed-off-by: Wenjing Liu Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 17 +- .../gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 337 +++++++++++++++++++++ drivers/gpu/drm/amd/display/dc/core/dc_state.c | 1 + drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h | 191 +++++++++++- drivers/gpu/drm/amd/display/dc/inc/core_types.h | 27 ++ drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h | 12 +- 6 files changed, 562 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index f4c5d5b14072..c4f5fdeb411c 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -8124,24 +8124,13 @@ void dc_log_preos_dmcub_info(const struct dc *dc) bool dc_get_qos_info(struct dc *dc, struct dc_qos_info *info) { const struct dc_clocks *clk = &dc->current_state->bw_ctx.bw.dcn.clk; - struct dc_measured_memory_qos measured = {}; struct dc_requested_memory_qos requested = {}; memset(info, 0, sizeof(*info)); - // Check if measurement function is available - if (!dc->hwss.measure_memory_qos) { - return false; - } - - dc->hwss.measure_memory_qos(dc, &measured); - - info->actual_peak_bw_in_mbps = measured.peak_bw_mbps; - info->actual_avg_bw_in_mbps = measured.avg_bw_mbps; - info->actual_min_latency_in_ns = measured.min_latency_ns; - info->actual_max_latency_in_ns = measured.max_latency_ns; - info->actual_avg_latency_in_ns = measured.avg_latency_ns; - info->dcn_bandwidth_ub_in_mbps = (uint32_t)(clk->fclk_khz / 1000 * 64); + /* TODO: remove the actual_* fields from struct dc_qos_info once all callers + * read measured QoS from dc_state probe_status instead of this struct. */ + info->dcn_bandwidth_ub_in_mbps = (uint32_t)(clk->fclk_khz / 1000 * 64); if (dc->clk_mgr && dc->clk_mgr->funcs->get_requested_memory_qos) { dc->clk_mgr->funcs->get_requested_memory_qos(dc->clk_mgr, &requested); diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c index 6002175420a0..07ec00e11f2d 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c @@ -1495,6 +1495,48 @@ void hwss_execute_sequence(struct dc *dc, case HUBBUB_SOFT_RESET: hwss_hubbub_soft_reset(params); break; + case HUBBUB_PERFMON_RESET: + hwss_hubbub_perfmon_reset(params); + break; + case HUBBUB_PERFMON_ARM_OUT_OF_ORDER_BW: + hwss_hubbub_perfmon_arm_out_of_order_bw(params); + break; + case HUBBUB_PERFMON_START_OUT_OF_ORDER_BW: + hwss_hubbub_perfmon_start_out_of_order_bw(params); + break; + case HUBBUB_PERFMON_START_IN_ORDER_BW: + hwss_hubbub_perfmon_start_in_order_bw(params); + break; + case HUBBUB_PERFMON_START_MEMORY_LATENCIES: + hwss_hubbub_perfmon_start_memory_latencies(params); + break; + case HUBBUB_PERFMON_START_URGENT_ASSERTION_COUNT: + hwss_hubbub_perfmon_start_urgent_assertion_count(params); + break; + case HUBBUB_PERFMON_START_URGENT_RAMP_LATENCY: + hwss_hubbub_perfmon_start_urgent_ramp_latency(params); + break; + case HUBBUB_PERFMON_START_PREFETCH_DATA_SIZE: + hwss_hubbub_perfmon_start_prefetch_data_size(params); + break; + case HUBBUB_PERFMON_GET_OUT_OF_ORDER_BW: + hwss_hubbub_perfmon_get_out_of_order_bw(params); + break; + case HUBBUB_PERFMON_GET_IN_ORDER_BW: + hwss_hubbub_perfmon_get_in_order_bw(params); + break; + case HUBBUB_PERFMON_GET_MEMORY_LATENCIES: + hwss_hubbub_perfmon_get_memory_latencies(params); + break; + case HUBBUB_PERFMON_GET_URGENT_ASSERTION_COUNT: + hwss_hubbub_perfmon_get_urgent_assertion_count(params); + break; + case HUBBUB_PERFMON_GET_PREFETCH_DATA_SIZE: + hwss_hubbub_perfmon_get_prefetch_data_size(params); + break; + case HUBBUB_PERFMON_GET_URGENT_RAMP_LATENCY: + hwss_hubbub_perfmon_get_urgent_ramp_latency(params); + break; case HUBP_CLK_CNTL: hwss_hubp_clk_cntl(params); break; @@ -3251,6 +3293,139 @@ void hwss_hubbub_soft_reset(union block_sequence_params *params) params->hubbub_soft_reset_params.hubbub_soft_reset(hubbub, reset); } +void hwss_hubbub_perfmon_reset(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_reset_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.reset) + hubbub->funcs->perfmon.reset(hubbub); +} + +void hwss_hubbub_perfmon_arm_out_of_order_bw(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_arm_out_of_order_bw_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.arm_measuring_out_of_order_bandwidth) + hubbub->funcs->perfmon.arm_measuring_out_of_order_bandwidth(hubbub); +} + +void hwss_hubbub_perfmon_start_out_of_order_bw(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_out_of_order_bw_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_out_of_order_bandwidth) + hubbub->funcs->perfmon.start_measuring_out_of_order_bandwidth(hubbub); +} + +void hwss_hubbub_perfmon_start_in_order_bw(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_in_order_bw_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_in_order_bandwidth) + hubbub->funcs->perfmon.start_measuring_in_order_bandwidth(hubbub); +} + +void hwss_hubbub_perfmon_start_memory_latencies(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_memory_latencies_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_memory_latencies) + hubbub->funcs->perfmon.start_measuring_memory_latencies(hubbub); +} + +void hwss_hubbub_perfmon_start_urgent_assertion_count(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_urgent_assertion_count_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_urgent_assertion_count) + hubbub->funcs->perfmon.start_measuring_urgent_assertion_count(hubbub); +} + +void hwss_hubbub_perfmon_start_urgent_ramp_latency(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_urgent_ramp_latency_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_urgent_ramp_latency) + hubbub->funcs->perfmon.start_measuring_urgent_ramp_latency( + hubbub, + ¶ms->hubbub_perfmon_start_urgent_ramp_latency_params.latency_params); +} + +void hwss_hubbub_perfmon_start_prefetch_data_size(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_start_prefetch_data_size_params.hubbub; + + if (hubbub && hubbub->funcs->perfmon.start_measuring_prefetch_data_size) + hubbub->funcs->perfmon.start_measuring_prefetch_data_size(hubbub); +} + +void hwss_hubbub_perfmon_get_out_of_order_bw(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_out_of_order_bw_params.hubbub; + uint32_t refclk_mhz = params->hubbub_perfmon_get_out_of_order_bw_params.refclk_mhz; + uint32_t *mbps = params->hubbub_perfmon_get_out_of_order_bw_params.bandwidth_mbps; + uint32_t *duration = params->hubbub_perfmon_get_out_of_order_bw_params.duration_ns; + + if (hubbub && hubbub->funcs->perfmon.get_out_of_order_bandwidth_mbps && mbps) + *mbps = hubbub->funcs->perfmon.get_out_of_order_bandwidth_mbps( + hubbub, refclk_mhz, duration); +} + +void hwss_hubbub_perfmon_get_in_order_bw(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_in_order_bw_params.hubbub; + uint32_t refclk_mhz = params->hubbub_perfmon_get_in_order_bw_params.refclk_mhz; + uint32_t min_duration_ns = params->hubbub_perfmon_get_in_order_bw_params.min_duration_ns; + uint32_t *mbps = params->hubbub_perfmon_get_in_order_bw_params.bandwidth_mbps; + uint32_t *duration = params->hubbub_perfmon_get_in_order_bw_params.duration_ns; + + if (hubbub && hubbub->funcs->perfmon.get_in_order_bandwidth_mbps && mbps) + *mbps = hubbub->funcs->perfmon.get_in_order_bandwidth_mbps( + hubbub, refclk_mhz, min_duration_ns, duration); +} + +void hwss_hubbub_perfmon_get_memory_latencies(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_memory_latencies_params.hubbub; + uint32_t refclk_mhz = params->hubbub_perfmon_get_memory_latencies_params.refclk_mhz; + struct hubbub_system_latencies *result = params->hubbub_perfmon_get_memory_latencies_params.result; + + if (hubbub && hubbub->funcs->perfmon.get_memory_latencies_ns && result) + hubbub->funcs->perfmon.get_memory_latencies_ns( + hubbub, refclk_mhz, &result->min_latency_ns, &result->max_latency_ns, &result->avg_latency_ns); +} + +void hwss_hubbub_perfmon_get_urgent_assertion_count(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_urgent_assertion_count_params.hubbub; + uint32_t refclk_mhz = params->hubbub_perfmon_get_urgent_assertion_count_params.refclk_mhz; + uint32_t *count = params->hubbub_perfmon_get_urgent_assertion_count_params.assertion_count; + + if (hubbub && hubbub->funcs->perfmon.get_urgent_assertion_count) + hubbub->funcs->perfmon.get_urgent_assertion_count( + hubbub, refclk_mhz, count, NULL, NULL); +} + +void hwss_hubbub_perfmon_get_prefetch_data_size(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_prefetch_data_size_params.hubbub; + uint32_t *prefetch_size = params->hubbub_perfmon_get_prefetch_data_size_params.prefetch_data_size; + + if (hubbub && hubbub->funcs->perfmon.get_prefetch_data_size && prefetch_size) + *prefetch_size = hubbub->funcs->perfmon.get_prefetch_data_size(hubbub); +} + +void hwss_hubbub_perfmon_get_urgent_ramp_latency(union block_sequence_params *params) +{ + struct hubbub *hubbub = params->hubbub_perfmon_get_urgent_ramp_latency_params.hubbub; + uint32_t refclk_mhz = params->hubbub_perfmon_get_urgent_ramp_latency_params.refclk_mhz; + uint32_t *latency_ns = params->hubbub_perfmon_get_urgent_ramp_latency_params.latency_ns; + + if (hubbub && hubbub->funcs->perfmon.get_urgent_ramp_latency_ns && latency_ns) + *latency_ns = hubbub->funcs->perfmon.get_urgent_ramp_latency_ns( + hubbub, refclk_mhz); +} + void hwss_hubp_clk_cntl(union block_sequence_params *params) { struct hubp *hubp = params->hubp_clk_cntl_params.hubp; @@ -4422,6 +4597,168 @@ void hwss_add_hubbub_soft_reset(struct block_sequence_state *seq_state, } } +void hwss_add_hubbub_perfmon_reset(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_RESET; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_reset_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_arm_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_ARM_OUT_OF_ORDER_BW; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_arm_out_of_order_bw_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_OUT_OF_ORDER_BW; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_out_of_order_bw_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_in_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_IN_ORDER_BW; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_in_order_bw_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_memory_latencies(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_MEMORY_LATENCIES; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_memory_latencies_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_urgent_assertion_count(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_URGENT_ASSERTION_COUNT; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_urgent_assertion_count_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_urgent_ramp_latency(struct block_sequence_state *seq_state, + struct hubbub *hubbub, + const struct hubbub_urgent_latency_params *latency_params) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_URGENT_RAMP_LATENCY; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_urgent_ramp_latency_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_urgent_ramp_latency_params.latency_params = + *latency_params; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_start_prefetch_data_size(struct block_sequence_state *seq_state, + struct hubbub *hubbub) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_START_PREFETCH_DATA_SIZE; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_start_prefetch_data_size_params.hubbub = hubbub; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *bandwidth_mbps, uint32_t *duration_ns) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_OUT_OF_ORDER_BW; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_out_of_order_bw_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_out_of_order_bw_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_out_of_order_bw_params.bandwidth_mbps = bandwidth_mbps; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_out_of_order_bw_params.duration_ns = duration_ns; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_in_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, uint32_t min_duration_ns, + uint32_t *bandwidth_mbps, uint32_t *duration_ns) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_IN_ORDER_BW; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_in_order_bw_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_in_order_bw_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_in_order_bw_params.min_duration_ns = min_duration_ns; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_in_order_bw_params.bandwidth_mbps = bandwidth_mbps; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_in_order_bw_params.duration_ns = duration_ns; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_memory_latencies(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + struct hubbub_system_latencies *result) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_MEMORY_LATENCIES; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_memory_latencies_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_memory_latencies_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_memory_latencies_params.result = result; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_urgent_assertion_count(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *assertion_count) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_URGENT_ASSERTION_COUNT; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_assertion_count_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_assertion_count_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_assertion_count_params.assertion_count = assertion_count; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_prefetch_data_size(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t *prefetch_data_size) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_PREFETCH_DATA_SIZE; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_prefetch_data_size_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_prefetch_data_size_params.prefetch_data_size = prefetch_data_size; + (*seq_state->num_steps)++; + } +} + +void hwss_add_hubbub_perfmon_get_urgent_ramp_latency(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *latency_ns) +{ + if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { + seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_URGENT_RAMP_LATENCY; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_ramp_latency_params.hubbub = hubbub; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_ramp_latency_params.refclk_mhz = refclk_mhz; + seq_state->steps[*seq_state->num_steps].params.hubbub_perfmon_get_urgent_ramp_latency_params.latency_ns = latency_ns; + (*seq_state->num_steps)++; + } +} + void hwss_add_hubp_clk_cntl(struct block_sequence_state *seq_state, struct hubp *hubp, bool enable) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_state.c b/drivers/gpu/drm/amd/display/dc/core/dc_state.c index 436e88f64f86..3e819997aafa 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_state.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_state.c @@ -361,6 +361,7 @@ void dc_state_destruct(struct dc_state *state) state->phantom_plane_count = 0; memset(state->probes, 0, sizeof(state->probes)); + memset(state->probe_status, 0, sizeof(state->probe_status)); state->probe_count = 0; state->stream_mask = 0; diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h index e8bf96a7d63a..6754da1e6ee2 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h @@ -32,6 +32,7 @@ #include "inc/hw/link_encoder.h" #include "inc/core_status.h" #include "inc/hw/hw_shared.h" +#include "inc/hw/dchubbub.h" #include "dsc/dsc.h" #include "link_service_types.h" @@ -53,7 +54,6 @@ struct drr_params; struct dc_underflow_debug_data; struct dsc_optc_config; struct vm_system_aperture_param; -struct dc_measured_memory_qos; struct stream_encoder; struct hpo_dp_stream_encoder; struct hpo_frl_stream_encoder; @@ -580,6 +580,77 @@ struct hubbub_soft_reset_params { bool reset; }; +struct hubbub_perfmon_reset_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_arm_out_of_order_bw_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_start_out_of_order_bw_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_start_in_order_bw_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_start_memory_latencies_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_start_urgent_assertion_count_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_start_urgent_ramp_latency_params { + struct hubbub *hubbub; + struct hubbub_urgent_latency_params latency_params; +}; + +struct hubbub_perfmon_start_prefetch_data_size_params { + struct hubbub *hubbub; +}; + +struct hubbub_perfmon_get_out_of_order_bw_params { + struct hubbub *hubbub; + uint32_t refclk_mhz; + uint32_t *bandwidth_mbps; + uint32_t *duration_ns; +}; + +struct hubbub_perfmon_get_in_order_bw_params { + struct hubbub *hubbub; + uint32_t refclk_mhz; + uint32_t min_duration_ns; + uint32_t *bandwidth_mbps; + uint32_t *duration_ns; +}; + +struct hubbub_perfmon_get_memory_latencies_params { + struct hubbub *hubbub; + uint32_t refclk_mhz; + struct hubbub_system_latencies *result; +}; + +struct hubbub_perfmon_get_urgent_assertion_count_params { + struct hubbub *hubbub; + uint32_t refclk_mhz; + uint32_t *assertion_count; +}; + +struct hubbub_perfmon_get_prefetch_data_size_params { + struct hubbub *hubbub; + uint32_t *prefetch_data_size; +}; + +struct hubbub_perfmon_get_urgent_ramp_latency_params { + struct hubbub *hubbub; + uint32_t refclk_mhz; + uint32_t *latency_ns; +}; + struct hubp_clk_cntl_params { struct hubp *hubp; bool enable; @@ -1031,6 +1102,20 @@ union block_sequence_params { struct hubp_set_blank_en_params hubp_set_blank_en_params; struct hubp_disable_control_params hubp_disable_control_params; struct hubbub_soft_reset_params hubbub_soft_reset_params; + struct hubbub_perfmon_reset_params hubbub_perfmon_reset_params; + struct hubbub_perfmon_arm_out_of_order_bw_params hubbub_perfmon_arm_out_of_order_bw_params; + struct hubbub_perfmon_start_out_of_order_bw_params hubbub_perfmon_start_out_of_order_bw_params; + struct hubbub_perfmon_start_in_order_bw_params hubbub_perfmon_start_in_order_bw_params; + struct hubbub_perfmon_start_memory_latencies_params hubbub_perfmon_start_memory_latencies_params; + struct hubbub_perfmon_start_urgent_assertion_count_params hubbub_perfmon_start_urgent_assertion_count_params; + struct hubbub_perfmon_start_urgent_ramp_latency_params hubbub_perfmon_start_urgent_ramp_latency_params; + struct hubbub_perfmon_start_prefetch_data_size_params hubbub_perfmon_start_prefetch_data_size_params; + struct hubbub_perfmon_get_out_of_order_bw_params hubbub_perfmon_get_out_of_order_bw_params; + struct hubbub_perfmon_get_in_order_bw_params hubbub_perfmon_get_in_order_bw_params; + struct hubbub_perfmon_get_memory_latencies_params hubbub_perfmon_get_memory_latencies_params; + struct hubbub_perfmon_get_urgent_assertion_count_params hubbub_perfmon_get_urgent_assertion_count_params; + struct hubbub_perfmon_get_prefetch_data_size_params hubbub_perfmon_get_prefetch_data_size_params; + struct hubbub_perfmon_get_urgent_ramp_latency_params hubbub_perfmon_get_urgent_ramp_latency_params; struct hubp_clk_cntl_params hubp_clk_cntl_params; struct hubp_init_params hubp_init_params; struct hubp_set_vm_system_aperture_settings_params hubp_set_vm_system_aperture_settings_params; @@ -1258,6 +1343,20 @@ enum block_sequence_func { HUBBUB_PROGRAM_WATERMARKS, HUBBUB_PROGRAM_ARBITER, HUBBUB_PROGRAM_COMPBUF_SEGMENTS, + HUBBUB_PERFMON_RESET, + HUBBUB_PERFMON_ARM_OUT_OF_ORDER_BW, + HUBBUB_PERFMON_START_OUT_OF_ORDER_BW, + HUBBUB_PERFMON_START_IN_ORDER_BW, + HUBBUB_PERFMON_START_MEMORY_LATENCIES, + HUBBUB_PERFMON_START_URGENT_ASSERTION_COUNT, + HUBBUB_PERFMON_START_URGENT_RAMP_LATENCY, + HUBBUB_PERFMON_START_PREFETCH_DATA_SIZE, + HUBBUB_PERFMON_GET_OUT_OF_ORDER_BW, + HUBBUB_PERFMON_GET_IN_ORDER_BW, + HUBBUB_PERFMON_GET_MEMORY_LATENCIES, + HUBBUB_PERFMON_GET_URGENT_ASSERTION_COUNT, + HUBBUB_PERFMON_GET_PREFETCH_DATA_SIZE, + HUBBUB_PERFMON_GET_URGENT_RAMP_LATENCY, /* This must be the last value in this enum, add new ones above */ HWSS_BLOCK_SEQUENCE_FUNC_COUNT }; @@ -1591,14 +1690,16 @@ struct hw_sequencer_funcs { struct dc_underflow_debug_data *out_data); /** - * measure_memory_qos - Measure memory QoS metrics - * @dc: DC structure - * @qos: Pointer to dc_measured_memory_qos struct to populate with measured values + * program_perfmon - Program/transition perfmon probes for a commit. + * @dc: DC structure + * @context: target state; probes, probe_count, and probe_status are + * read from and written to this object * - * Populates the provided dc_measured_memory_qos struct with peak bandwidth, average bandwidth, - * max latency, min latency, and average latency from hardware performance counters. + * Invoked during the execute phase of dc_update_state. The hook resolves + * each probe's transition by diffing @context against dc->current_state + * and latches MEASURED results into @context->probe_status. */ - void (*measure_memory_qos)(struct dc *dc, struct dc_measured_memory_qos *qos); + void (*program_perfmon)(struct dc *dc, struct dc_state *context); }; @@ -1875,6 +1976,34 @@ void hwss_hubp_disable_control(union block_sequence_params *params); void hwss_hubbub_soft_reset(union block_sequence_params *params); +void hwss_hubbub_perfmon_reset(union block_sequence_params *params); + +void hwss_hubbub_perfmon_arm_out_of_order_bw(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_out_of_order_bw(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_in_order_bw(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_memory_latencies(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_urgent_assertion_count(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_urgent_ramp_latency(union block_sequence_params *params); + +void hwss_hubbub_perfmon_start_prefetch_data_size(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_out_of_order_bw(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_in_order_bw(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_memory_latencies(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_urgent_assertion_count(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_prefetch_data_size(union block_sequence_params *params); + +void hwss_hubbub_perfmon_get_urgent_ramp_latency(union block_sequence_params *params); + void hwss_hubp_clk_cntl(union block_sequence_params *params); void hwss_hubp_init(union block_sequence_params *params); @@ -2213,6 +2342,54 @@ void hwss_add_hubbub_soft_reset(struct block_sequence_state *seq_state, void (*hubbub_soft_reset)(struct hubbub *hubbub, bool reset), bool reset); +void hwss_add_hubbub_perfmon_reset(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_arm_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_start_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_start_in_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_start_memory_latencies(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_start_urgent_assertion_count(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_start_urgent_ramp_latency(struct block_sequence_state *seq_state, + struct hubbub *hubbub, + const struct hubbub_urgent_latency_params *latency_params); + +void hwss_add_hubbub_perfmon_start_prefetch_data_size(struct block_sequence_state *seq_state, + struct hubbub *hubbub); + +void hwss_add_hubbub_perfmon_get_out_of_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *bandwidth_mbps, uint32_t *duration_ns); + +void hwss_add_hubbub_perfmon_get_in_order_bw(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, uint32_t min_duration_ns, + uint32_t *bandwidth_mbps, uint32_t *duration_ns); + +void hwss_add_hubbub_perfmon_get_memory_latencies(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + struct hubbub_system_latencies *result); + +void hwss_add_hubbub_perfmon_get_urgent_assertion_count(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *assertion_count); + +void hwss_add_hubbub_perfmon_get_prefetch_data_size(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t *prefetch_data_size); + +void hwss_add_hubbub_perfmon_get_urgent_ramp_latency(struct block_sequence_state *seq_state, + struct hubbub *hubbub, uint32_t refclk_mhz, + uint32_t *latency_ns); + void hwss_add_hubp_clk_cntl(struct block_sequence_state *seq_state, struct hubp *hubp, bool enable); diff --git a/drivers/gpu/drm/amd/display/dc/inc/core_types.h b/drivers/gpu/drm/amd/display/dc/inc/core_types.h index c42626101cd7..ac3e9eaa569c 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/core_types.h +++ b/drivers/gpu/drm/amd/display/dc/inc/core_types.h @@ -33,6 +33,7 @@ #include "dc_bios_types.h" #include "mem_input.h" #include "hubp.h" +#include "hw/dchubbub.h" #include "mpc.h" #include "dwb.h" #include "hw/dio.h" @@ -609,6 +610,27 @@ struct dc_dmub_cmd { enum dm_dmub_wait_type wait_type; }; +/** + * struct dc_probe_status - DC-internal latched perfmon results for a probe. + * @valid: true if a measurement was latched this commit. + * @type: type of the probe that produced this result. + * @u.bandwidth_mbps: peak BW in Mbps (DC_PROBE_PEAK_MEM_BW). + * @u.latency: min/max/avg memory latency in ns (DC_PROBE_MEM_LATENCY), + * stored as struct hubbub_system_latencies. + * @u.urgent_assertion_count: number of urgent assertion events (DC_PROBE_URGENT_ASSERTION_COUNT). + * @u.prefetch_data_size: total prefetch data in bytes (DC_PROBE_PREFETCH_DATA_SIZE). + */ +struct dc_probe_status { + bool valid; + enum dc_probe_type type; + union { + uint32_t bandwidth_mbps; + struct hubbub_system_latencies latency; + uint32_t urgent_assertion_count; + uint32_t prefetch_data_size; + } u; +}; + /** * struct dc_state - The full description of a state requested by users */ @@ -652,6 +674,11 @@ struct dc_state { */ struct dc_probe_state probes[MAX_PROBES]; + /** + * @probe_status: Committed absolute set of probe results. + */ + struct dc_probe_status probe_status[MAX_PROBES]; + /** * @probe_count: Number of valid entries in @probes. */ diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h b/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h index 4307362749f0..fd742b320128 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h @@ -158,6 +158,12 @@ struct hubbub_urgent_latency_params { uint32_t bw_factor_x1000; }; +struct hubbub; +struct dchub_init_data; +struct dc_dcc_surface_param; +struct dc_surface_dcc_cap; +union dcn_watermark_set; +struct dml2_display_arb_regs; struct hubbub_funcs { void (*update_dchub)( struct hubbub *hubbub, @@ -274,9 +280,11 @@ struct hubbub_funcs { const struct hubbub_urgent_latency_params *params); uint32_t (*get_urgent_ramp_latency_ns)(struct hubbub *hubbub, uint32_t refclk_mhz); - void (*start_measuring_unbounded_bandwidth)( + void (*arm_measuring_out_of_order_bandwidth)( struct hubbub *hubbub); - uint32_t (*get_unbounded_bandwidth_mbps)(struct hubbub *hubbub, + void (*start_measuring_out_of_order_bandwidth)( + struct hubbub *hubbub); + uint32_t (*get_out_of_order_bandwidth_mbps)(struct hubbub *hubbub, uint32_t refclk_mhz, uint32_t *duration_ns); void (*start_measuring_in_order_bandwidth)( struct hubbub *hubbub); -- cgit v1.2.3 From 52771a3254eabb37de2d53aa7b623a5487776ee8 Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Wed, 24 Jun 2026 23:17:40 -0400 Subject: drm/amd/display: Wire probe commit path into dc_update_state [Why] The dc_update_state commit interface and the dc_probe object model existed but were not connected: there was no path to validate, commit, and program a probe through the update pipeline, nor to read the result back. The measurement waits on OTG frame edges and must not block fast updates. [How] - Move the per-commit scratch off dc_stream_state onto a dc-owned pool; acquire at init, release on cleanup or prepare failure, serialized by the DM global lock every commit path already holds. - dc_update_state drives stream commits, probe-only commits, or both through one staged init/prepare/execute/cleanup loop with a null-arg guard. prepare releases the scratch slot on failure; cleanup releases it on success. - Probe prepare installs the absolute probe set in place; execute calls the perfmon programming hook in the unlocked window and latches results. - Add resource_validate_probe_set, a context-free achievability check shared by dc_validate_global_state (full update) and probe prepare (probe-only). - dc_state_get_status gains a probe status class with a probe filter and a by-type mapping; dc_validation_set gains probes/probe_count. Drop the dead actual_* fields from struct dc_qos_info. Reviewed-by: Dominik Kaszewski Signed-off-by: Wenjing Liu Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 314 +++++++++++++++------ .../gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 4 +- drivers/gpu/drm/amd/display/dc/core/dc_resource.c | 38 +++ drivers/gpu/drm/amd/display/dc/core/dc_state.c | 11 + drivers/gpu/drm/amd/display/dc/core/dc_stream.c | 22 +- drivers/gpu/drm/amd/display/dc/dc.h | 122 +++++++- drivers/gpu/drm/amd/display/dc/dc_stream.h | 27 -- drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h | 4 +- drivers/gpu/drm/amd/display/dc/inc/core_types.h | 56 ++-- drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h | 6 - drivers/gpu/drm/amd/display/dc/inc/resource.h | 4 + 11 files changed, 428 insertions(+), 180 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index c4f5fdeb411c..78408fc60eb8 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -898,6 +898,32 @@ void dc_stream_set_static_screen_params(struct dc *dc, dc->hwss.set_static_screen_control(pipes_affected, num_pipes_affected, params); } +static void dc_destruct_update_scratch_pool(struct dc *dc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dc->update_scratch_pool); i++) { + kfree(dc->update_scratch_pool[i]); + dc->update_scratch_pool[i] = NULL; + dc->update_scratch_in_use[i] = false; + } +} + +static bool dc_construct_update_scratch_pool(struct dc *dc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dc->update_scratch_pool); i++) { + dc->update_scratch_pool[i] = kzalloc( + sizeof(struct dc_update_scratch_space), GFP_KERNEL); + if (!dc->update_scratch_pool[i]) + return false; + dc->update_scratch_in_use[i] = false; + } + + return true; +} + static void dc_destruct(struct dc *dc) { // reset link encoder assignment table on destruct @@ -905,6 +931,8 @@ static void dc_destruct(struct dc *dc) !dc->config.unify_link_enc_assignment) link_enc_cfg_init(dc, dc->current_state); + dc_destruct_update_scratch_pool(dc); + if (dc->current_state) { dc_state_release(dc->current_state); dc->current_state = NULL; @@ -1148,6 +1176,11 @@ static bool dc_construct(struct dc *dc, goto fail; } + if (!dc_construct_update_scratch_pool(dc)) { + dm_error("%s: failed to create update scratch pool\n", __func__); + goto fail; + } + return true; fail: @@ -3234,11 +3267,6 @@ static struct dc_update_descriptor check_update_surfaces_for_stream( return overall_type; } -/* - * dc_check_update_surfaces_for_stream() - Determine update type (fast, med, or full) - * - * See :c:type:`enum dc_update_type ` for explanation of update types - */ /** * dc_check_state_update - Classify a dc_state_update by locking / re-entrancy requirements. * @check_config: ASIC capabilities and display configuration context @@ -3246,7 +3274,8 @@ static struct dc_update_descriptor check_update_surfaces_for_stream( * * Determines whether the update requires a fast, medium, or full lock * by inspecting the stream, stream_update, and surface_updates carried on - * the root object. Perfmon classification is reserved for a future slice. + * the root object. A probe update elevates the result to at least MED with + * the PROBE lock, so a probe-carrying commit takes the probe mutex. * * Return: dc_update_descriptor with update_type and lock_descriptor. */ @@ -3254,13 +3283,20 @@ struct dc_update_descriptor dc_check_state_update( const struct dc_check_config *check_config, struct dc_state_update *updates) { + struct dc_update_descriptor desc = {0}; + if (updates->stream_update) stream_update_flags_clear(&updates->stream_update->stream->update_flags); for (int i = 0; i < updates->surface_count; i++) dc_pipe_update_bits_clear(&updates->surface_updates[i].surface->update_bits); - return check_update_surfaces_for_stream(check_config, updates->surface_updates, + desc = check_update_surfaces_for_stream(check_config, updates->surface_updates, updates->surface_count, updates->stream_update); + + if (updates->probe_updates && updates->probe_updates->probe_count > 0) + elevate_update_type(&desc, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_PROBE); + + return desc; } /** @@ -3282,9 +3318,11 @@ struct dc_update_descriptor dc_check_update_surfaces_for_stream( struct dc_stream_update *stream_update) { struct dc_state_update root = { + .stream = stream_update ? stream_update->stream : NULL, + .stream_update = stream_update, .surface_updates = updates, .surface_count = surface_count, - .stream_update = stream_update, + .probe_updates = NULL }; return dc_check_state_update(check_config, &root); @@ -3703,13 +3741,6 @@ static bool full_update_required_weak( const struct dc_stream_update *stream_update, const struct dc_stream_state *stream); -struct pipe_split_policy_backup { - bool dynamic_odm_policy; - bool subvp_policy; - enum pipe_split_policy mpc_policy; - char force_odm[MAX_PIPES]; -}; - static void backup_and_set_minimal_pipe_split_policy(struct dc *dc, struct dc_state *context, struct pipe_split_policy_backup *policy) @@ -6196,30 +6227,60 @@ static void clear_update_bits(struct dc_surface_update *srf_updates, dc_pipe_update_bits_clear(&srf_updates[i].surface->update_bits); } +static struct dc_update_scratch_space *dc_update_scratch_acquire(struct dc *dc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dc->update_scratch_pool); i++) { + if (dc->update_scratch_in_use[i]) + continue; + + dc->update_scratch_in_use[i] = true; + return dc->update_scratch_pool[i]; + } + + /* TODO: add recoverable scratch acquisition failure handling. */ + ASSERT(false); + return NULL; +} + +static void dc_update_scratch_release(struct dc *dc, + struct dc_update_scratch_space *scratch) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dc->update_scratch_pool); i++) { + if (dc->update_scratch_pool[i] == scratch) { + dc->update_scratch_in_use[i] = false; + return; + } + } +} + /** * dc_update_state - Commit an absolute dc_state_update. * @dc: DC structure * @updates: root update object carrying stream, plane, and probe updates - * - * When stream is non-NULL the stream and its plane updates are committed via - * the init/prepare/execute/cleanup pipeline. Probe commit is reserved for a - * future slice. dc_update_planes_and_stream() is now a shim over this function. - * * Return: true on success, false on failure. */ bool dc_update_state(struct dc *dc, struct dc_state_update *updates) { - if (updates->stream != NULL) { - struct dc_update_scratch_space *scratch = dc_update_state_init(dc, updates); - bool more = true; + struct dc_update_scratch_space *scratch; + bool more = true; - while (more) { - if (!dc_update_state_prepare(scratch)) - return false; + if (!dc || !updates) + return false; - dc_update_state_execute(scratch); - more = dc_update_state_cleanup(scratch); - } + scratch = dc_update_state_init(dc, updates); + if (!scratch) + return false; + + while (more) { + if (!dc_update_state_prepare(scratch)) + return false; + + dc_update_state_execute(scratch); + more = dc_update_state_cleanup(scratch); } return true; @@ -8128,8 +8189,6 @@ bool dc_get_qos_info(struct dc *dc, struct dc_qos_info *info) memset(info, 0, sizeof(*info)); - /* TODO: remove the actual_* fields from struct dc_qos_info once all callers - * read measured QoS from dc_state probe_status instead of this struct. */ info->dcn_bandwidth_ub_in_mbps = (uint32_t)(clk->fclk_khz / 1000 * 64); if (dc->clk_mgr && dc->clk_mgr->funcs->get_requested_memory_qos) { @@ -8155,38 +8214,6 @@ unsigned int dc_override_memory_bandwidth_request( dc->clk_mgr, bw_mbps * 1000) / 1000; } -enum update_v3_flow { - UPDATE_V3_FLOW_INVALID, - UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FAST, - UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FULL, - UPDATE_V3_FLOW_NEW_CONTEXT_SEAMLESS, - UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_NEW, - UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_CURRENT, -}; - -struct dc_update_scratch_space { - struct dc *dc; - struct dc_surface_update *surface_updates; - int surface_count; - struct dc_stream_state *stream; - struct dc_stream_update *stream_update; - bool update_v3; - bool do_clear_update_bits; - enum dc_update_type update_type; - struct dc_state *new_context; - enum update_v3_flow flow; - struct dc_state *backup_context; - struct dc_state *intermediate_context; - struct pipe_split_policy_backup intermediate_policy; - struct dc_surface_update intermediate_updates[MAX_SURFACES]; - int intermediate_count; -}; - -size_t dc_update_scratch_space_size(void) -{ - return sizeof(struct dc_update_scratch_space); -} - static bool update_planes_and_stream_prepare_v2( struct dc_update_scratch_space *scratch ) @@ -8353,6 +8380,19 @@ static bool update_planes_and_stream_prepare_v3( return false; } +/** + * should_commit_intermediate_context - Does this flow commit a transient + * minimal-transition intermediate context + * @flow: the commit flow selected for this iteration + * + * Return: true if this iteration commits the intermediate context. + */ +static bool should_commit_intermediate_context(enum update_v3_flow flow) +{ + return flow == UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_NEW + || flow == UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_CURRENT; +} + static void update_planes_and_stream_execute_v3_commit( const struct dc_update_scratch_space *scratch, bool intermediate_update, @@ -8376,6 +8416,8 @@ static void update_planes_and_stream_execute_v3( const struct dc_update_scratch_space *scratch ) { + bool intermediate_context = should_commit_intermediate_context(scratch->flow); + switch (scratch->flow) { case UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FAST: commit_planes_for_stream_fast( @@ -8391,16 +8433,16 @@ static void update_planes_and_stream_execute_v3( case UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FULL: case UPDATE_V3_FLOW_NEW_CONTEXT_SEAMLESS: - update_planes_and_stream_execute_v3_commit(scratch, false, false, true); + update_planes_and_stream_execute_v3_commit(scratch, false, intermediate_context, true); break; case UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_NEW: - update_planes_and_stream_execute_v3_commit(scratch, false, true, + update_planes_and_stream_execute_v3_commit(scratch, false, intermediate_context, scratch->dc->check_config.deferred_transition_state); break; case UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_CURRENT: - update_planes_and_stream_execute_v3_commit(scratch, true, true, false); + update_planes_and_stream_execute_v3_commit(scratch, true, intermediate_context, false); break; case UPDATE_V3_FLOW_INVALID: @@ -8481,45 +8523,139 @@ struct dc_update_scratch_space *dc_update_state_init( ) { const enum dce_version version = dc->ctx->dce_version; - struct dc_update_scratch_space *scratch = updates->stream->update_scratch; - - *scratch = (struct dc_update_scratch_space){ - .dc = dc, - .surface_updates = updates->surface_updates, - .surface_count = updates->surface_count, - .stream = updates->stream, - .stream_update = updates->stream_update, - .update_v3 = version >= DCN_VERSION_4_01 || version == DCN_VERSION_3_2 || version == DCN_VERSION_3_21, - .do_clear_update_bits = version >= DCN_VERSION_1_0, - }; + struct dc_update_scratch_space *scratch = dc_update_scratch_acquire(dc); + bool has_stream_or_plane = updates->stream || updates->stream_update || updates->surface_updates; + bool has_probe = updates->probe_updates; + bool surface_without_stream = updates->surface_updates && !updates->stream; + bool stream_update_without_stream = updates->stream_update && !updates->stream; + bool bad_surface_count = updates->surface_count > 0 && !updates->surface_updates; + + if (!scratch) + return NULL; + + if (!has_stream_or_plane && !has_probe) { + dc_update_scratch_release(dc, scratch); + return NULL; + } + + if (surface_without_stream || stream_update_without_stream || bad_surface_count) { + dc_update_scratch_release(dc, scratch); + return NULL; + } + + memset(scratch, 0, sizeof(*scratch)); + + scratch->dc = dc; + scratch->surface_updates = updates->surface_updates; + scratch->surface_count = updates->surface_count; + scratch->stream = updates->stream; + scratch->stream_update = updates->stream_update; + scratch->probe_updates = updates->probe_updates; + scratch->update_v3 = version >= DCN_VERSION_4_01 + || version == DCN_VERSION_3_2 + || version == DCN_VERSION_3_21; + scratch->do_clear_update_bits = version >= DCN_VERSION_1_0; + scratch->new_context = NULL; + scratch->flow = UPDATE_V3_FLOW_INVALID; return scratch; } -bool dc_update_state_prepare( - struct dc_update_scratch_space *scratch -) +/** + * dc_update_probes_prepare - Commit the desired probe set into new_context. + * @scratch: commit scratch carrying the probe updates + * + * Return: true on success or when there is nothing to do; false when the + * desired set is unachievable. + */ +static bool dc_update_probes_prepare(struct dc_update_scratch_space *scratch) +{ + struct dc *dc = scratch->dc; + const struct dc_probe_updates *probe_updates = scratch->probe_updates; + uint8_t i; + + if (!probe_updates) + return true; + + if (resource_validate_probe_set(dc, probe_updates->probes, + (uint8_t)probe_updates->probe_count) != DC_OK) + return false; + + if (!scratch->new_context) + scratch->new_context = dc->current_state; + + for (i = 0; i < probe_updates->probe_count && i < MAX_PROBES; i++) + scratch->new_context->probes[i] = probe_updates->probes[i]; + scratch->new_context->probe_count = probe_updates->probe_count; + + return true; +} + +/** + * dc_update_probes_execute - Program the committed probes. + * @scratch: commit scratch carrying the probe updates + * + */ +static void dc_update_probes_execute(const struct dc_update_scratch_space *scratch) +{ + struct dc *dc = scratch->dc; + + if (should_commit_intermediate_context(scratch->flow)) + return; + + if (dc->hwss.program_perfmon) + dc->hwss.program_perfmon(dc, scratch->new_context); +} + +bool dc_update_state_prepare(struct dc_update_scratch_space *scratch) { - return scratch->update_v3 - ? update_planes_and_stream_prepare_v3(scratch) - : update_planes_and_stream_prepare_v2(scratch); + if (scratch->stream) { + bool ok = scratch->update_v3 + ? update_planes_and_stream_prepare_v3(scratch) + : update_planes_and_stream_prepare_v2(scratch); + + if (!ok) + goto release_scratch; + } + + if (!dc_update_probes_prepare(scratch)) + goto release_scratch; + + return true; + +release_scratch: + /* execute and cleanup never run on this path, so release here. */ + dc_update_scratch_release(scratch->dc, scratch); + return false; } void dc_update_state_execute( const struct dc_update_scratch_space *scratch ) { - scratch->update_v3 - ? update_planes_and_stream_execute_v3(scratch) - : update_planes_and_stream_execute_v2(scratch); + if (scratch->stream) + scratch->update_v3 + ? update_planes_and_stream_execute_v3(scratch) + : update_planes_and_stream_execute_v2(scratch); + + if (scratch->probe_updates) + dc_update_probes_execute(scratch); } bool dc_update_state_cleanup( struct dc_update_scratch_space *scratch ) { - return scratch->update_v3 - ? update_planes_and_stream_cleanup_v3(scratch) - : update_planes_and_stream_cleanup_v2(scratch); + bool more = false; + + if (scratch->stream) + more = scratch->update_v3 + ? update_planes_and_stream_cleanup_v3(scratch) + : update_planes_and_stream_cleanup_v2(scratch); + + if (!more) + dc_update_scratch_release(scratch->dc, scratch); + + return more; } diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c index 07ec00e11f2d..8b54bab98283 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c @@ -3388,7 +3388,7 @@ void hwss_hubbub_perfmon_get_memory_latencies(union block_sequence_params *param { struct hubbub *hubbub = params->hubbub_perfmon_get_memory_latencies_params.hubbub; uint32_t refclk_mhz = params->hubbub_perfmon_get_memory_latencies_params.refclk_mhz; - struct hubbub_system_latencies *result = params->hubbub_perfmon_get_memory_latencies_params.result; + struct dc_probe_latencies *result = params->hubbub_perfmon_get_memory_latencies_params.result; if (hubbub && hubbub->funcs->perfmon.get_memory_latencies_ns && result) hubbub->funcs->perfmon.get_memory_latencies_ns( @@ -4711,7 +4711,7 @@ void hwss_add_hubbub_perfmon_get_in_order_bw(struct block_sequence_state *seq_st void hwss_add_hubbub_perfmon_get_memory_latencies(struct block_sequence_state *seq_state, struct hubbub *hubbub, uint32_t refclk_mhz, - struct hubbub_system_latencies *result) + struct dc_probe_latencies *result) { if (*seq_state->num_steps < MAX_HWSS_BLOCK_SEQUENCE_SIZE) { seq_state->steps[*seq_state->num_steps].func = HUBBUB_PERFMON_GET_MEMORY_LATENCIES; diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c index d9492a460c2a..b970f152d67f 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c @@ -4383,6 +4383,40 @@ static bool add_all_planes_for_stream( return true; } +/** + * resource_validate_probe_set - Validate a probe descriptor set against the ASIC. + * @dc: DC instance providing the HWSS capability hooks + * @probes: desired probe descriptors + * @probe_count: number of valid entries in @probes + * + * Return: DC_OK if achievable, otherwise a DC error. + */ +enum dc_status resource_validate_probe_set(struct dc *dc, + const struct dc_probe_state *probes, + uint8_t probe_count) +{ + uint8_t i; + + if (probe_count == 0) + return DC_OK; + + if (!dc->hwss.program_perfmon) + return DC_NOT_SUPPORTED; + + if (probe_count > MAX_PROBES) + return DC_NOT_SUPPORTED; + + for (i = 0; i < probe_count; i++) { + if (probes[i].target_state == DC_PROBE_MEASURING) + return DC_NOT_SUPPORTED; + + if (probes[i].scope.type != DC_PROBE_SCOPE_GLOBAL) + return DC_NOT_SUPPORTED; + } + + return DC_OK; +} + /** * dc_validate_with_context - Validate and update the potential new stream in the context object * @@ -4754,6 +4788,10 @@ enum dc_status dc_validate_global_state( if (result == DC_OK) result = dc->res_pool->funcs->validate_bandwidth(dc, new_ctx, validate_mode); + if (result == DC_OK) + result = resource_validate_probe_set(dc, new_ctx->probes, + (uint8_t)new_ctx->probe_count); + return result; } diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_state.c b/drivers/gpu/drm/amd/display/dc/core/dc_state.c index 3e819997aafa..b534b1a641af 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_state.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_state.c @@ -708,6 +708,17 @@ enum dc_status dc_state_get_status(struct dc_state_status *status, } } + if (options->types & DC_GET_STATUS_PROBE) { + status->probe_count = 0; + for (i = 0; i < options->state->probe_count; i++) { + if (options->probe && + options->probe->type != options->state->probes[i].type) + continue; + status->probe_status[status->probe_count++] = + &options->state->probe_status[i]; + } + } + return DC_OK; } diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c index 5a46d91e2f37..25f904eccb10 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c @@ -194,7 +194,6 @@ static void dc_stream_free(struct kref *kref) struct dc_stream_state *stream = container_of(kref, struct dc_stream_state, refcount); dc_stream_destruct(stream); - kfree(stream->update_scratch); kfree(stream); } @@ -219,13 +218,6 @@ struct dc_stream_state *dc_create_stream_for_sink( if (stream == NULL) goto fail; - DC_RUN_WITH_PREEMPTION_ENABLED(stream->update_scratch = - kzalloc((int32_t) dc_update_scratch_space_size(), - GFP_ATOMIC)); - - if (stream->update_scratch == NULL) - goto fail; - if (dc_stream_construct(stream, sink) == false) goto fail; @@ -234,10 +226,8 @@ struct dc_stream_state *dc_create_stream_for_sink( return stream; fail: - if (stream) { - kfree(stream->update_scratch); + if (stream) kfree(stream); - } return NULL; } @@ -250,16 +240,6 @@ struct dc_stream_state *dc_copy_stream(const struct dc_stream_state *stream) if (!new_stream) return NULL; - // Scratch is not meant to be reused across copies, as might have self-referential pointers - new_stream->update_scratch = kzalloc( - (int32_t) dc_update_scratch_space_size(), - GFP_KERNEL - ); - if (!new_stream->update_scratch) { - kfree(new_stream); - return NULL; - } - if (new_stream->sink) dc_sink_retain(new_stream->sink); diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 2da89c7470de..0913fab7504c 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -530,6 +530,7 @@ enum dc_lock_descriptor { LOCK_DESCRIPTOR_STREAM = 0x1, LOCK_DESCRIPTOR_LINK = 0x2, LOCK_DESCRIPTOR_GLOBAL = 0x4, + LOCK_DESCRIPTOR_PROBE = 0x8, }; struct dc_update_descriptor { @@ -1018,14 +1019,9 @@ struct dc_bounding_box_overrides { }; struct dc_qos_info { - uint32_t actual_peak_bw_in_mbps; uint32_t qos_bandwidth_lb_in_mbps; - uint32_t actual_avg_bw_in_mbps; uint32_t calculated_avg_bw_in_mbps; - uint32_t actual_max_latency_in_ns; - uint32_t actual_min_latency_in_ns; uint32_t qos_max_latency_ub_in_ns; - uint32_t actual_avg_latency_in_ns; uint32_t qos_avg_latency_ub_in_ns; uint32_t dcn_bandwidth_ub_in_mbps; uint32_t qos_max_bw_budget_in_mbps; @@ -1987,6 +1983,12 @@ struct dc { struct dc_state *current_state; struct resource_pool *res_pool; + /** + * @update_scratch_pool: Per-commit scratch buffers for dc_update_state. + */ + struct dc_update_scratch_space *update_scratch_pool[MAX_STREAMS + 1]; + bool update_scratch_in_use[MAX_STREAMS + 1]; + struct clk_mgr *clk_mgr; /* Display Engine Clock levels */ @@ -2114,25 +2116,107 @@ struct dc_state_update { const struct dc_probe_updates *probe_updates; }; +/** + * dc_check_state_update() - Classify an update without committing it. + * @check_config: DC check configuration + * @updates: root update object to classify + * + * Return: descriptor indicating update type and required lock scope. + */ +struct dc_update_descriptor dc_check_state_update( + const struct dc_check_config *check_config, + struct dc_state_update *updates); + /** * dc_update_state - Commit an absolute dc_state_update. * @dc: DC structure * @updates: root update object carrying stream, plane, and probe updates * - * When stream is non-NULL the stream and its plane updates are committed via - * the init/prepare/execute/cleanup pipeline. Probe commit is reserved for a - * future slice. dc_update_planes_and_stream() is now a shim over this function. - * * Return: true on success, false on failure. */ bool dc_update_state(struct dc *dc, struct dc_state_update *updates); +struct dc_update_scratch_space; + +/** + * dc_update_state_init - Acquire and initialise a commit scratch buffer. + * @dc: DC structure + * @updates: update descriptor; validated before the slot is acquired + * + * Return: a scratch slot on success, NULL if validation fails or the pool + * is exhausted. The slot must be released via dc_update_state_cleanup() on + * success, or automatically by dc_update_state_prepare() on failure. + */ +struct dc_update_scratch_space *dc_update_state_init( + struct dc *dc, + const struct dc_state_update *updates +); + +/** + * dc_update_state_prepare - Prepare the commit under the global lock. + * @scratch: commit scratch from dc_update_state_init() + * + * On failure the scratch slot is released and false is returned; the caller + * must not call execute or cleanup. + */ +bool dc_update_state_prepare(struct dc_update_scratch_space *scratch); + +/** + * dc_update_state_execute - Program hardware; called without the global lock. + * @scratch: commit scratch from dc_update_state_init() + */ +void dc_update_state_execute(const struct dc_update_scratch_space *scratch); + +/** + * dc_update_state_cleanup - Finalise the commit and release the scratch slot. + * @scratch: commit scratch from dc_update_state_init() + * + * Must be called with the global lock held. Returns true if the caller must + * loop back to prepare (SEAMLESS continuation). + */ +bool dc_update_state_cleanup(struct dc_update_scratch_space *scratch); + +/** + * struct dc_probe_latencies - min/max/avg memory latency in ns. + * @max_latency_ns: maximum latency in nanoseconds + * @avg_latency_ns: average latency in nanoseconds + * @min_latency_ns: minimum latency in nanoseconds + */ +struct dc_probe_latencies { + uint32_t max_latency_ns; + uint32_t avg_latency_ns; + uint32_t min_latency_ns; +}; + +/** + * struct dc_probe_status - results for a probe. + * @valid: true if a measurement was latched. + * @type: type of the probe that produced this result. + * @u.bandwidth_mbps: peak BW in Mbps (DC_PROBE_PEAK_MEM_BW). + * @u.latency: min/max/avg memory latency in ns (DC_PROBE_MEM_LATENCY), + * stored as struct dc_probe_latencies. + * @u.urgent_assertion_count: number of urgent assertion events (DC_PROBE_URGENT_ASSERTION_COUNT). + * @u.prefetch_data_size: total prefetch data in bytes (DC_PROBE_PREFETCH_DATA_SIZE). + */ +struct dc_probe_status { + bool valid; + enum dc_probe_type type; + union { + uint32_t bandwidth_mbps; + struct dc_probe_latencies latency; + uint32_t urgent_assertion_count; + uint32_t prefetch_data_size; + } u; +}; + /** * enum dc_get_status_type - Bitmask selecting which status classes to populate. * @DC_GET_STATUS_STREAM: populate stream_status fields in dc_state_status + * @DC_GET_STATUS_PROBE: populate probe_status fields in dc_state_status */ enum dc_get_status_type { DC_GET_STATUS_STREAM = (1u << 0), + DC_GET_STATUS_PROBE = (1u << 1), }; /** @@ -2141,21 +2225,28 @@ enum dc_get_status_type { * @types: OR of dc_get_status_type values selecting classes to populate * @stream: optional stream filter for DC_GET_STATUS_STREAM. NULL means * populate status for all streams in the state + * @probe: optional probe filter for DC_GET_STATUS_PROBE. NULL means + * populate status for all probes in the state */ struct dc_get_status_options { struct dc_state *state; uint32_t types; const struct dc_stream_state *stream; + const struct dc_probe_state *probe; }; /** * struct dc_state_status - Output-only status object from dc_state_get_status. * @stream_count: number of valid entries in stream_status (DC_GET_STATUS_STREAM) * @stream_status: pointers to live per-stream status entries + * @probe_count: number of valid entries in probe_status (DC_GET_STATUS_PROBE) + * @probe_status: pointers to live per-probe status entries */ struct dc_state_status { int stream_count; struct dc_stream_status *stream_status[MAX_STREAMS]; + int probe_count; + struct dc_probe_status *probe_status[MAX_PROBES]; }; /** @@ -2163,9 +2254,6 @@ struct dc_state_status { * @status: output object populated according to options->types * @options: selects the source state, status classes to fill, and filters * - * dc_state_get_stream_status() is a thin shim over this function with - * types = DC_GET_STATUS_STREAM and a stream filter. - * * Return: DC_OK on success, DC_ERROR_UNEXPECTED if state is NULL. */ enum dc_status dc_state_get_status(struct dc_state_status *status, @@ -2258,6 +2346,16 @@ struct dc_validation_set { * @stream_count: Number of active entries in @streams */ uint8_t stream_count; + + /** + * @probes: Global probe descriptors to validate alongside the streams + */ + struct dc_probe_state probes[MAX_PROBES]; + + /** + * @probe_count: Number of active entries in @probes + */ + uint8_t probe_count; }; bool dc_validate_boot_timing(const struct dc *dc, diff --git a/drivers/gpu/drm/amd/display/dc/dc_stream.h b/drivers/gpu/drm/amd/display/dc/dc_stream.h index a866688ad9db..4530f294f1c2 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_stream.h +++ b/drivers/gpu/drm/amd/display/dc/dc_stream.h @@ -329,7 +329,6 @@ struct dc_stream_state { enum dc_drr_trigger_mode drr_trigger_mode; - struct dc_update_scratch_space *update_scratch; bool firmware_controlled_hdr_info_packet; }; @@ -408,30 +407,8 @@ bool dc_update_planes_and_stream(struct dc *dc, struct dc_stream_state *dc_stream, struct dc_stream_update *stream_update); -struct dc_update_scratch_space; struct dc_state_update; -size_t dc_update_scratch_space_size(void); - -struct dc_update_scratch_space *dc_update_state_init( - struct dc *dc, - const struct dc_state_update *updates -); - -// Locked, false is failed -bool dc_update_state_prepare( - struct dc_update_scratch_space *scratch -); - -// Unlocked -void dc_update_state_execute( - const struct dc_update_scratch_space *scratch -); - -// Locked, true if call again -bool dc_update_state_cleanup( - struct dc_update_scratch_space *scratch -); /* * Set up surface attributes and associate to a stream @@ -516,10 +493,6 @@ void dc_enable_stereo( /* Triggers multi-stream synchronization. */ void dc_trigger_sync(struct dc *dc, struct dc_state *context); -struct dc_update_descriptor dc_check_state_update( - const struct dc_check_config *check_config, - struct dc_state_update *updates); - /* Shim: packs args into dc_state_update and calls dc_check_state_update(). */ struct dc_update_descriptor dc_check_update_surfaces_for_stream( const struct dc_check_config *check_config, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h index 6754da1e6ee2..4549c435501a 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h @@ -631,7 +631,7 @@ struct hubbub_perfmon_get_in_order_bw_params { struct hubbub_perfmon_get_memory_latencies_params { struct hubbub *hubbub; uint32_t refclk_mhz; - struct hubbub_system_latencies *result; + struct dc_probe_latencies *result; }; struct hubbub_perfmon_get_urgent_assertion_count_params { @@ -2377,7 +2377,7 @@ void hwss_add_hubbub_perfmon_get_in_order_bw(struct block_sequence_state *seq_st void hwss_add_hubbub_perfmon_get_memory_latencies(struct block_sequence_state *seq_state, struct hubbub *hubbub, uint32_t refclk_mhz, - struct hubbub_system_latencies *result); + struct dc_probe_latencies *result); void hwss_add_hubbub_perfmon_get_urgent_assertion_count(struct block_sequence_state *seq_state, struct hubbub *hubbub, uint32_t refclk_mhz, diff --git a/drivers/gpu/drm/amd/display/dc/inc/core_types.h b/drivers/gpu/drm/amd/display/dc/inc/core_types.h index ac3e9eaa569c..e87f9fd60bcb 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/core_types.h +++ b/drivers/gpu/drm/amd/display/dc/inc/core_types.h @@ -610,27 +610,6 @@ struct dc_dmub_cmd { enum dm_dmub_wait_type wait_type; }; -/** - * struct dc_probe_status - DC-internal latched perfmon results for a probe. - * @valid: true if a measurement was latched this commit. - * @type: type of the probe that produced this result. - * @u.bandwidth_mbps: peak BW in Mbps (DC_PROBE_PEAK_MEM_BW). - * @u.latency: min/max/avg memory latency in ns (DC_PROBE_MEM_LATENCY), - * stored as struct hubbub_system_latencies. - * @u.urgent_assertion_count: number of urgent assertion events (DC_PROBE_URGENT_ASSERTION_COUNT). - * @u.prefetch_data_size: total prefetch data in bytes (DC_PROBE_PREFETCH_DATA_SIZE). - */ -struct dc_probe_status { - bool valid; - enum dc_probe_type type; - union { - uint32_t bandwidth_mbps; - struct hubbub_system_latencies latency; - uint32_t urgent_assertion_count; - uint32_t prefetch_data_size; - } u; -}; - /** * struct dc_state - The full description of a state requested by users */ @@ -774,4 +753,39 @@ struct dc_requested_memory_qos { uint32_t max_bw_budget_in_mbps; }; +enum update_v3_flow { + UPDATE_V3_FLOW_INVALID, + UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FAST, + UPDATE_V3_FLOW_NO_NEW_CONTEXT_CONTEXT_FULL, + UPDATE_V3_FLOW_NEW_CONTEXT_SEAMLESS, + UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_NEW, + UPDATE_V3_FLOW_NEW_CONTEXT_MINIMAL_CURRENT, +}; + +struct pipe_split_policy_backup { + bool dynamic_odm_policy; + bool subvp_policy; + enum pipe_split_policy mpc_policy; + char force_odm[MAX_PIPES]; +}; + +struct dc_update_scratch_space { + struct dc *dc; + struct dc_surface_update *surface_updates; + int surface_count; + struct dc_stream_state *stream; + struct dc_stream_update *stream_update; + const struct dc_probe_updates *probe_updates; + bool update_v3; + bool do_clear_update_bits; + enum dc_update_type update_type; + struct dc_state *new_context; + enum update_v3_flow flow; + struct dc_state *backup_context; + struct dc_state *intermediate_context; + struct pipe_split_policy_backup intermediate_policy; + struct dc_surface_update intermediate_updates[MAX_SURFACES]; + int intermediate_count; +}; + #endif /* _CORE_TYPES_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h b/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h index fd742b320128..02bc0010e565 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/dchubbub.h @@ -145,12 +145,6 @@ struct dcn_hubbub_reg_state { uint32_t compbuf_ctrl; }; -struct hubbub_system_latencies { - uint32_t max_latency_ns; - uint32_t avg_latency_ns; - uint32_t min_latency_ns; -}; - struct hubbub_urgent_latency_params { uint32_t refclk_mhz; uint32_t t_win_ns; diff --git a/drivers/gpu/drm/amd/display/dc/inc/resource.h b/drivers/gpu/drm/amd/display/dc/inc/resource.h index b64ba8c0adb1..d3171e70e07c 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/resource.h +++ b/drivers/gpu/drm/amd/display/dc/inc/resource.h @@ -582,6 +582,10 @@ bool resource_validate_attach_surfaces( struct dc_state *context, const struct resource_pool *pool); +enum dc_status resource_validate_probe_set(struct dc *dc, + const struct dc_probe_state *probes, + uint8_t probe_count); + enum dc_status resource_map_clock_resources( const struct dc *dc, struct dc_state *context, -- cgit v1.2.3 From a63f63f473e0ad53351c99e9602ee00c8428af2a Mon Sep 17 00:00:00 2001 From: Wenjing Liu Date: Tue, 7 Jul 2026 17:15:48 -0400 Subject: drm/amd/display: Make dc_state_update const in commit path [Why] The state-update commit path only reads the caller's update descriptor, it never mutates the dc_state_update root. Making the pointer const documents that contract. [How] Add const to the updates parameter of dc_update_state and dc_check_state_update. Mark the single-assignment locals in dc_update_state_init const and replace the memset plus field-by-field assignment with a compound literal initializer. Reviewed-by: Dominik Kaszewski Signed-off-by: Wenjing Liu Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 42 ++++++++++++++++---------------- drivers/gpu/drm/amd/display/dc/dc.h | 4 +-- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 78408fc60eb8..01a469f34ad4 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -3281,7 +3281,7 @@ static struct dc_update_descriptor check_update_surfaces_for_stream( */ struct dc_update_descriptor dc_check_state_update( const struct dc_check_config *check_config, - struct dc_state_update *updates) + const struct dc_state_update *updates) { struct dc_update_descriptor desc = {0}; @@ -6263,7 +6263,7 @@ static void dc_update_scratch_release(struct dc *dc, * @updates: root update object carrying stream, plane, and probe updates * Return: true on success, false on failure. */ -bool dc_update_state(struct dc *dc, struct dc_state_update *updates) +bool dc_update_state(struct dc *dc, const struct dc_state_update *updates) { struct dc_update_scratch_space *scratch; bool more = true; @@ -8524,11 +8524,11 @@ struct dc_update_scratch_space *dc_update_state_init( { const enum dce_version version = dc->ctx->dce_version; struct dc_update_scratch_space *scratch = dc_update_scratch_acquire(dc); - bool has_stream_or_plane = updates->stream || updates->stream_update || updates->surface_updates; - bool has_probe = updates->probe_updates; - bool surface_without_stream = updates->surface_updates && !updates->stream; - bool stream_update_without_stream = updates->stream_update && !updates->stream; - bool bad_surface_count = updates->surface_count > 0 && !updates->surface_updates; + const bool has_stream_or_plane = updates->stream || updates->stream_update || updates->surface_updates; + const bool has_probe = updates->probe_updates; + const bool surface_without_stream = updates->surface_updates && !updates->stream; + const bool stream_update_without_stream = updates->stream_update && !updates->stream; + const bool bad_surface_count = updates->surface_count > 0 && !updates->surface_updates; if (!scratch) return NULL; @@ -8543,20 +8543,20 @@ struct dc_update_scratch_space *dc_update_state_init( return NULL; } - memset(scratch, 0, sizeof(*scratch)); - - scratch->dc = dc; - scratch->surface_updates = updates->surface_updates; - scratch->surface_count = updates->surface_count; - scratch->stream = updates->stream; - scratch->stream_update = updates->stream_update; - scratch->probe_updates = updates->probe_updates; - scratch->update_v3 = version >= DCN_VERSION_4_01 - || version == DCN_VERSION_3_2 - || version == DCN_VERSION_3_21; - scratch->do_clear_update_bits = version >= DCN_VERSION_1_0; - scratch->new_context = NULL; - scratch->flow = UPDATE_V3_FLOW_INVALID; + *scratch = (struct dc_update_scratch_space){ + .dc = dc, + .surface_updates = updates->surface_updates, + .surface_count = updates->surface_count, + .stream = updates->stream, + .stream_update = updates->stream_update, + .probe_updates = updates->probe_updates, + .update_v3 = version >= DCN_VERSION_4_01 + || version == DCN_VERSION_3_2 + || version == DCN_VERSION_3_21, + .do_clear_update_bits = version >= DCN_VERSION_1_0, + .new_context = NULL, + .flow = UPDATE_V3_FLOW_INVALID, + }; return scratch; } diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 0913fab7504c..743dde3d10ab 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -2125,7 +2125,7 @@ struct dc_state_update { */ struct dc_update_descriptor dc_check_state_update( const struct dc_check_config *check_config, - struct dc_state_update *updates); + const struct dc_state_update *updates); /** * dc_update_state - Commit an absolute dc_state_update. @@ -2134,7 +2134,7 @@ struct dc_update_descriptor dc_check_state_update( * * Return: true on success, false on failure. */ -bool dc_update_state(struct dc *dc, struct dc_state_update *updates); +bool dc_update_state(struct dc *dc, const struct dc_state_update *updates); struct dc_update_scratch_space; -- cgit v1.2.3 From 53845307d52e383970f4255253780a8730f3d9be Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Thu, 9 Jul 2026 14:47:38 -0400 Subject: drm/amd/display: Register DCN as a PMFW DF C-state client on DCN42 [Why] DCN must not gate DF C-state locally via the DCHUBBUB ALLOW_SELF_REFRESH controls: forcing a local "disallow" during MM-stutter re-entry can wedge the fabric and hang boot. When DCN is disallowing c-state or has invalid watermarks we should be explicit about it rather than using the watermark force selector. [How] Register DCN as a client of PMFW's DF C-state arbiter and signal over DALSMC whether DCN permits DF C-state; PMFW allows DF C-state only once every client (including DCN) has voted "allow". - dc_clocks.cstate_allow: last DCN vote acked by PMFW - clk_mgr_funcs::notify_cstate_disable(clk_mgr, disable) prepare_bandwidth and headless dc_power_down_on_boot vote Allow; hardware_release votes Disallow; init_clocks (D0 entry) only clears the cache (cstate_allow) so the next allow re-syncs with PMFW. Reviewed-by: Nicholas Kazlauskas Signed-off-by: Leo Chen Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c | 28 +++++++++++++++++++++ .../amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.h | 1 + .../drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.c | 29 +++++++++++++++++++++- .../drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.h | 2 ++ drivers/gpu/drm/amd/display/dc/core/dc.c | 3 +++ drivers/gpu/drm/amd/display/dc/dc.h | 9 +++++++ .../drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 10 ++++++++ drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h | 10 ++++++++ 8 files changed, 91 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c index 3baac7fa313a..c7b9bad93a93 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c @@ -375,6 +375,24 @@ void dcn42_enable_pme_wa(struct clk_mgr *clk_mgr_base) dcn42_smu_enable_pme_wa(clk_mgr); } +void dcn42_notify_cstate_disable(struct clk_mgr *clk_mgr_base, bool disable) +{ + struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base); + bool target_allow = !disable; + + DC_LOGGER_INIT(clk_mgr_base->ctx->logger); + + /* Idempotent: only send when the cached vote actually changes. */ + if (clk_mgr_base->clks.cstate_allow == target_allow) + return; + + if (dcn42_smu_set_df_cstate_disable(clk_mgr, disable)) + clk_mgr_base->clks.cstate_allow = target_allow; + else + DC_LOG_WARNING("%s: PMFW did not ack DfCstateDisable(%s); leaving cstate_allow=%d to retry\n", + __func__, disable ? "Disable" : "Allow", clk_mgr_base->clks.cstate_allow); +} + bool dcn42_are_clock_states_equal(struct dc_clocks *a, struct dc_clocks *b) @@ -594,6 +612,15 @@ void dcn42_init_clocks(struct clk_mgr *clk_mgr_base) init_clk_states(clk_mgr_base); + /* + * DF C-state policy + * D0 entry must NOT send a PMFW message, but must unconditionally clear + * the cached vote so the next allow-side transition (prepare_bandwidth + * or dc_power_down_on_boot) is guaranteed to issue a fresh + * DfCstateDisable(Allow) and resync DAL with PMFW. + */ + clk_mgr_base->clks.cstate_allow = false; + // to adjust dp_dto reference clock if ssc is enable otherwise to apply dprefclk if (dcn42_is_spll_ssc_enabled(clk_mgr_base)) clk_mgr_base->dp_dto_source_clock_in_khz = @@ -1038,6 +1065,7 @@ static struct clk_mgr_funcs dcn42_funcs = { .get_max_clock_khz = dcn42_get_max_clock_khz, .get_dispclk_from_dentist = dcn42_get_dispclk_from_dentist, .is_smu_present = dcn42_is_smu_present, + .notify_cstate_disable = dcn42_notify_cstate_disable, }; struct clk_mgr_funcs dcn42_fpga_funcs = { diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.h b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.h index 9568ca06f00f..330242747fff 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.h +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.h @@ -67,6 +67,7 @@ unsigned int dcn42_convert_wck_ratio(uint8_t wck_ratio); extern struct dcn42_ss_info_table dcn42_ss_info_table; void dcn42_build_watermark_ranges(struct clk_bw_params *bw_params, struct dcn42_watermarks *table); void dcn42_enable_pme_wa(struct clk_mgr *clk_mgr_base); +void dcn42_notify_cstate_disable(struct clk_mgr *clk_mgr_base, bool disable); void dcn42_notify_wm_ranges(struct clk_mgr *clk_mgr_base); void dcn42_set_low_power_state(struct clk_mgr *clk_mgr_base); void dcn42_exit_low_power_state(struct clk_mgr *clk_mgr_base); diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.c index 6d0012b7d6dc..5e8d979f25f0 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.c @@ -85,7 +85,8 @@ #define DALSMC_MSG_DispIPS2Exit 0x11 ///< Display IPS2 exit #define DALSMC_MSG_QueryIPS2Support 0x12 ///< Return 1: support; else not supported -#define DALSMC_Message_Count 0x13 ///< Total number of VBIS and DAL messages +#define DALSMC_MSG_DfCstateDisable 0x13 ///< DCN DF C-state vote (PMFW FWDEV-193711): param 0 = Allow, 1 = Disable +#define DALSMC_Message_Count 0x14 ///< Total number of VBIS and DAL messages /** @}*/ @@ -428,3 +429,29 @@ void dcn42_smu_set_dtbclk(struct clk_mgr_internal *clk_mgr, bool enable) enable); smu_print("%s: smu_set_dtbclk = %d\n", __func__, enable ? 1 : 0); } + +/* + * Vote DCN's DF C-state policy to PMFW. param: 0 = Allow, 1 = Disable. + * Returns true only when PMFW acknowledged the vote (or there is no SMU to + * talk to, in which case there is no DF arbiter to satisfy). On a non-OK + * response the caller must NOT update its cached cstate_allow so the next + * transition retries + */ +bool dcn42_smu_set_df_cstate_disable(struct clk_mgr_internal *clk_mgr, bool disable) +{ + int retv; + + if (!clk_mgr->smu_present) + return true; + + retv = dcn42_smu_send_msg_with_param( + clk_mgr, + DALSMC_MSG_DfCstateDisable, + disable ? 1 : 0); + + smu_print("%s: DfCstateDisable param = %d, return = %d\n", + __func__, disable ? 1 : 0, retv); + + /* dcn42_smu_send_msg_with_param() returns -1 on a non-OK PMFW response. */ + return retv != -1; +} diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.h b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.h index 8ba7ff04dc05..10c67326ff7e 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.h +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_smu.h @@ -187,4 +187,6 @@ void dcn42_vbios_smu_enable_48mhz_tmdp_refclk_pwrdwn(struct clk_mgr_internal *cl int dcn42_smu_get_dtbclk(struct clk_mgr_internal *clk_mgr); int dcn42_smu_get_dprefclk(struct clk_mgr_internal *clk_mgr); +bool dcn42_smu_set_df_cstate_disable(struct clk_mgr_internal *clk_mgr, bool disable); + #endif /* DAL_DC_42_SMU_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 01a469f34ad4..6c1a8ecc2cb5 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -6543,6 +6543,9 @@ void dc_power_down_on_boot(struct dc *dc) if (dc->caps.ips_support) dc_exit_ips_for_hw_access(dc); dc->hwss.power_down_on_boot(dc); + + if (dc->clk_mgr && dc->clk_mgr->funcs && dc->clk_mgr->funcs->notify_cstate_disable) + dc->clk_mgr->funcs->notify_cstate_disable(dc->clk_mgr, false); } } diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 743dde3d10ab..fcaa17b9ab1a 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -759,6 +759,15 @@ struct dc_clocks { * Elements below are not compared for the purposes of * optimization required */ + + /* + * @cstate_allow + * + * DCN's DF C-state vote as last successfully acknowledged by PMFW. + * false = DCN does NOT permit DF C-state; + * true = DCN permits DF C-state; + */ + bool cstate_allow; bool prev_p_state_change_support; bool fclk_prev_p_state_change_support; int num_ways; diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c index 4376bf26f4ce..151a29bf0e9d 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c @@ -946,7 +946,11 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx, } void dcn42_hardware_release(struct dc *dc) { + if (dc->clk_mgr && dc->clk_mgr->funcs && dc->clk_mgr->funcs->notify_cstate_disable) + dc->clk_mgr->funcs->notify_cstate_disable(dc->clk_mgr, true); + dcn35_hardware_release(dc); + dc_dmub_srv_release_hw(dc); } @@ -1082,6 +1086,12 @@ void dcn42_prepare_bandwidth( } dcn401_prepare_bandwidth(dc, context); + + /* valid C-state watermarks have now been committed to HW, so it + * is safe to vote "allow" to PMFW. + */ + if (dc->clk_mgr && dc->clk_mgr->funcs && dc->clk_mgr->funcs->notify_cstate_disable) + dc->clk_mgr->funcs->notify_cstate_disable(dc->clk_mgr, false); } void dcn42_optimize_bandwidth(struct dc *dc, struct dc_state *context) diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h index 68dc2d4ba7ca..ec678bd249ef 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h @@ -412,6 +412,16 @@ struct clk_mgr_funcs { struct clk_mgr *clk_mgr, struct dc_requested_memory_qos *qos); + /** + * notify_cstate_disable - Vote DCN's DF C-state policy to PMFW. + * @disable: true -> vote "disable" + * false -> vote "allow" + * Sends the message only when the cached dc_clocks.cstate_allow would + * change, then updates the cache on an OK response (idempotent no-op + * otherwise). + */ + void (*notify_cstate_disable)(struct clk_mgr *clk_mgr, bool disable); + void (*build_clock_update_for_bls)(struct clk_mgr *clk_mgr, struct dc_state *context, bool safe_to_lower, struct block_sequence_state *seq_state); -- cgit v1.2.3 From 49521be4809d63fe3efb6bc68ee11cb1e1ef3d63 Mon Sep 17 00:00:00 2001 From: Jerry Zuo Date: Fri, 10 Jul 2026 13:14:26 -0400 Subject: drm/amd/display: hide Apple Studio Display secondary tile The Apple Studio Display exposes a 2x1 tiled panel over two SST DP links. The primary tile advertises the full 5120x2880 mode (with DSC on the bandwidth-sufficient link) while the secondary carries a per-tile 2560x2880 timing on a link without sufficient bandwidth. Report the non-primary tile connector as disconnected during detect so compositors only see the primary DP link and configure a single 5K mode instead of driving both tiled streams independently. Drive the behaviour from an EDID quirk: add a disable_second_tile panel patch that apply_edid_quirks() sets for the affected Apple Studio Display panel IDs (0xAE3A, 0xAE42, 0xAE46), and have detect() hide the secondary tile when the sink carries that quirk. Reviewed-by: Mario Limonciello Reviewed-by: Sun peng Li Signed-off-by: Jerry Zuo Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 36 ++++++++++++++++++++++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 11 +++++++ drivers/gpu/drm/amd/display/dc/dc_types.h | 1 + 3 files changed, 48 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index b22ded941391..caabf9ed367c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1643,6 +1643,39 @@ amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) } EXPORT_IF_KUNIT(amdgpu_dm_connector_poll); +/* + * Apple Studio Display exposes two SST DP links for a 2x1 tiled panel. + * The primary tile advertises the full 5120x2880 mode (with DSC on the + * bandwidth-sufficient link) while the secondary carries a per-tile + * 2560x2880 timing on a insufficient bandwidth link. Hide the secondary + * connector from userspace so compositors configure a single 5K stream + * on the primary link only. + */ +static bool amdgpu_dm_hide_secondary_tile_from_userspace(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + + if (!aconnector->dc_sink) + return false; + + if (!aconnector->dc_sink->edid_caps.panel_patch.disable_second_tile) + return false; + + drm_edid_connector_update(connector, aconnector->drm_edid); + + if (!connector->has_tile) + return false; + + if (!connector->tile_h_loc && !connector->tile_v_loc) + return false; + + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] hiding secondary Apple Studio Display tile from userspace\n", + connector->base.id, connector->name); + + return true; +} + /** * amdgpu_dm_connector_detect() - Detect whether a DRM connector is connected to a display * @@ -1686,6 +1719,9 @@ amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) (!aconnector->dc_sink || aconnector->dc_sink->edid_caps.analog)) return amdgpu_dm_connector_poll(aconnector, force); + if (amdgpu_dm_hide_secondary_tile_from_userspace(connector)) + return connector_status_disconnected; + return (aconnector->dc_sink ? connector_status_connected : connector_status_disconnected); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 7413da51415d..c720c319e795 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -136,6 +136,17 @@ STATIC_IFN_KUNIT void apply_edid_quirks(struct dc_link *link, struct edid *edid, drm_dbg_driver(dev, "Skip PHY SSC reduction on panel id %X\n", panel_id); link->wa_flags.skip_phy_ssc_reduction = true; break; + /* + * Workaround for Apple Studio Display which exposes a 2x1 tiled panel + * over two SST DP links. Hide the secondary tile from userspace so + * compositors drive a single 5K stream on the primary link only. + */ + case drm_edid_encode_panel_id('A', 'P', 'P', 0xAE3A): + case drm_edid_encode_panel_id('A', 'P', 'P', 0xAE42): + case drm_edid_encode_panel_id('A', 'P', 'P', 0xAE46): + drm_dbg_driver(dev, "Hiding secondary tile on panel id %X\n", panel_id); + edid_caps->panel_patch.disable_second_tile = true; + break; default: return; } diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h index fccf9cb359f0..3edeb94fba23 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h @@ -188,6 +188,7 @@ struct dc_panel_patch { unsigned int skip_audio_sab_check; unsigned int mst_start_top_delay; unsigned int remove_sink_ext_caps; + bool disable_second_tile; unsigned int disable_colorimetry; uint8_t blankstream_before_otg_off; bool oled_optimize_display_on; -- cgit v1.2.3 From 5a19fd061bbd67549c3a5025205b94a8c30c1f10 Mon Sep 17 00:00:00 2001 From: Clara Wong Date: Wed, 24 Jun 2026 11:25:09 -0400 Subject: drm/amd/display: Reduce DML reinitialization when params don't change [WHY] Reinitializing DML causes an extra ~8s delay in gaming mode preset switches on some systems [HOW] Don't reinitialize DML unless params/caps have changed Reviewed-by: Austin Zheng Reviewed-by: Taimur Hassan Signed-off-by: Clara Wong Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c index a975d36ce15d..4e3d54c92e32 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c @@ -45,7 +45,22 @@ void dml21_init(const struct dc *in_dc, struct dml2_context *dml_ctx, const stru void dml21_reinit(const struct dc *in_dc, struct dml2_context *dml_ctx, const struct dml2_configuration_options *config) { - dml21_init(in_dc, dml_ctx, config); + struct dml2_instance *dml2 = dml_ctx->v21.dml_init.dml2_instance; + + dml21_populate_configuration_options(in_dc, dml_ctx, config); + + dml21_populate_dml_init_params(&dml_ctx->v21.dml_init, &dml_ctx->config, in_dc); + + // Skip full re-initialization if soc_bb, ip_caps and pmo_options are unchanged + if (memcmp(&dml2->soc_bbox, &dml_ctx->v21.dml_init.soc_bb, + sizeof(struct dml2_soc_bb)) == 0 && + memcmp(&dml2->ip_caps, &dml_ctx->v21.dml_init.ip_caps, + sizeof(struct dml2_ip_capabilities)) == 0 && + memcmp(&dml2->pmo_options, &dml_ctx->v21.dml_init.options.pmo_options, + sizeof(struct dml2_pmo_options)) == 0) + return; + + dml2_initialize_instance(&dml_ctx->v21.dml_init); } static void dml21_calculate_rq_and_dlg_params(const struct dc *dc, struct dc_state *context, struct resource_context *out_new_hw_state, -- cgit v1.2.3 From 7b5e55ba1dbfd5f3e89ebb7111d49df5282506c5 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Mon, 11 May 2026 23:56:27 -0400 Subject: drm/amd/display: Add DCHUBBUB_HW_DEBUG offset/mask Add missing register defines for DCN42B. Reviewed-by: Ovidiu Bunea Signed-off-by: Matthew Stewart Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.h b/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.h index 5ae9fb76c675..ddc614c98234 100644 --- a/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.h +++ b/drivers/gpu/drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.h @@ -340,7 +340,12 @@ HUBBUB_SF(DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_Z8_D, DCHUBBUB_ARB_ALLOW_SR_EXIT_WATERMARK_Z8_D, mask_sh), \ HUBBUB_SF(DCHUBBUB_ARB_DRAM_STATE_CNTL, DCHUBBUB_ARB_ALLOW_CSTATE_DEEPSLEEP_LEGACY_MODE, mask_sh), \ HUBBUB_SF(DCHUBBUB_ARB_HOSTVM_CNTL, DCHUBBUB_ARB_MAX_QOS_COMMIT_THRESHOLD, mask_sh),\ - HUBBUB_SF(DCHUBBUB_ARB_DF_REQ_OUTSTAND, DCHUBBUB_ARB_MIN_REQ_OUTSTAND_COMMIT_THRESHOLD, mask_sh) + HUBBUB_SF(DCHUBBUB_ARB_DF_REQ_OUTSTAND, DCHUBBUB_ARB_MIN_REQ_OUTSTAND_COMMIT_THRESHOLD, mask_sh), \ + HUBBUB_SF(DCHUBBUB_CTRL_STATUS, DCHUBBUB_HW_DEBUG, mask_sh) + +#define DCHUBBUB_CTRL_STATUS__DCHUBBUB_HW_DEBUG__SHIFT 0x4 + +#define DCHUBBUB_CTRL_STATUS__DCHUBBUB_HW_DEBUG_MASK 0x3FFFFFF0L void hubbub35_construct(struct dcn20_hubbub *hubbub2, struct dc_context *ctx, -- cgit v1.2.3 From 957f52eb5c13b5eba5de9df3a71bb1a154aac8fa Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 13 Jul 2026 11:18:40 -0600 Subject: drm/amd/display: Fix missing dc_3dlut forward declaration [WHY] The __set_colorop_3dlut() prototype in the KUnit-test section of amdgpu_dm_color.h references struct dc_3dlut, but no forward declaration for that struct exists in the header. The forward declaration was originally present but was repurposed into struct dc_plane_cm when the adjacent amdgpu_dm_atomic_lut3d() prototype was updated, leaving __set_colorop_3dlut() without a declaration for struct dc_3dlut. [HOW] Add back forward declaration of struct dc_3dlut alongside the other forward declarations at the top of the header. Fixes: b008c67efb36 ("drm/amd/display: Introduce dc_plane_cm and migrate surface update color path") Cc: Alex Hung Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h index 1a8b06bdaf44..cec23a020c3d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h @@ -44,6 +44,7 @@ struct dc_plane_state; struct fixed31_32; struct tetrahedral_params; struct dc_transfer_func; +struct dc_3dlut; #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) /* -- cgit v1.2.3 From 4bba99a69a94528f7cd9477c6cbb849e0129fd35 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 13 Jul 2026 11:34:47 -0600 Subject: drm/amd/display: Flush IRQ workqueue in schedule-work tests [WHAT] The tests dm_test_irq_schedule_work_queues_handler, dm_test_irq_schedule_work_requeue_fallback, and dm_test_irq_handler_dispatches_work relied on amdgpu_dm_irq_fini() running each pending low-context work item before freeing the handlers, and only checked the handler counts afterwards. amdgpu_dm_irq_fini() now cancels pending work with cancel_work_sync() instead of flushing it, so work that has not yet started never runs and the counts stay below the expected values, failing the tests. Flush the private DM IRQ workqueue (adev->dm.irq_wq) so the scheduled handlers complete, check the counts, then tear down. Flushing this driver-owned workqueue is allowed, unlike the system-wide workqueues. Fixes: 0d6453fd6e17 ("drm/amd/display: Fix DM IRQ teardown races") Cc: Geoffrey McRae Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_irq_test.c | 31 +++++++++++++++------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index ed20e278742d..dc7ef0523b8f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -1842,12 +1842,15 @@ static void dm_test_irq_schedule_work_queues_handler(struct kunit *test) amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); /* - * Low-context work runs asynchronously on system_highpri_wq. - * amdgpu_dm_irq_fini() flushes each pending work item before freeing - * the handlers, so the handler is guaranteed to have run afterwards. + * Low-context work runs asynchronously on the DM IRQ workqueue. + * Flush it so the handler completes before we check the count; + * amdgpu_dm_irq_fini() cancels (rather than runs) any work that is + * still pending, so the flush must happen first. */ - amdgpu_dm_irq_fini(adev); + flush_workqueue(adev->dm.irq_wq); KUNIT_EXPECT_EQ(test, count, 1); + + amdgpu_dm_irq_fini(adev); } /** @@ -1858,7 +1861,7 @@ static void dm_test_irq_schedule_work_queues_handler(struct kunit *test) * schedule before the work has run makes queue_work() fail for the * still-pending item, forcing amdgpu_dm_irq_schedule_work() into the fallback * that allocates and queues a fresh handler copy. Both work items run when - * amdgpu_dm_irq_fini() flushes the queue, so the handler fires twice. + * the DM IRQ workqueue is flushed, so the handler fires twice. */ static void dm_test_irq_schedule_work_requeue_fallback(struct kunit *test) { @@ -1880,8 +1883,15 @@ static void dm_test_irq_schedule_work_requeue_fallback(struct kunit *test) amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); amdgpu_dm_irq_schedule_work(adev, DC_IRQ_SOURCE_HPD1); - amdgpu_dm_irq_fini(adev); + /* + * Flush the DM IRQ workqueue so both work items run before we check + * the count; amdgpu_dm_irq_fini() would cancel any still-pending work + * instead of running it. + */ + flush_workqueue(adev->dm.irq_wq); KUNIT_EXPECT_EQ(test, count, 2); + + amdgpu_dm_irq_fini(adev); } /* Tests for amdgpu_dm_set_hpd_irq_state() */ @@ -3855,11 +3865,14 @@ static void dm_test_irq_handler_dispatches_work(struct kunit *test) KUNIT_EXPECT_EQ(test, high_count, 1); /* - * Low-context work runs asynchronously; amdgpu_dm_irq_fini() flushes - * each pending work item before freeing, so it has run afterwards. + * Low-context work runs asynchronously; flush the DM IRQ workqueue so + * it completes before we check the count. amdgpu_dm_irq_fini() cancels + * any still-pending work rather than running it. */ - amdgpu_dm_irq_fini(adev); + flush_workqueue(adev->dm.irq_wq); KUNIT_EXPECT_EQ(test, low_count, 1); + + amdgpu_dm_irq_fini(adev); } /* Tests for dm_handle_vmin_vmax_update() */ -- cgit v1.2.3 From 8cba48e7a813eacf241470ead5301bfcb680513a Mon Sep 17 00:00:00 2001 From: Wayne Lin Date: Wed, 15 Jul 2026 15:20:07 +0800 Subject: drm/amd/display: Add SPL UPSP upsampling and YUV422 scaling support - Add YUV422/YUV444 pixel format definitions - Handle YUV422 chroma scaling ratios - Use separate horizontal and vertical viewport divisors - Add UPSP upsampling register programming - Refine tap selection for horizontally and vertically subsampled formats Reviewed-by: Tom Chung Signed-off-by: Aurabindo Pillai Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/sspl/dc_spl.c | 202 ++++++++++++++++++--- drivers/gpu/drm/amd/display/dc/sspl/dc_spl_types.h | 58 +++++- 2 files changed, 234 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/sspl/dc_spl.c b/drivers/gpu/drm/amd/display/dc/sspl/dc_spl.c index d8aebaff7c3f..1f1318769d5b 100644 --- a/drivers/gpu/drm/amd/display/dc/sspl/dc_spl.c +++ b/drivers/gpu/drm/amd/display/dc/sspl/dc_spl.c @@ -9,7 +9,6 @@ #define IDENTITY_RATIO(ratio) (SPL_NAMESPACE(spl_fixpt_u3d19(ratio)) == (1 << 19)) #define MIN_VIEWPORT_SIZE 12 - static bool spl_is_yuv420(enum spl_pixel_format format) { if ((format >= SPL_PIXEL_FORMAT_420BPP8) && @@ -19,6 +18,15 @@ static bool spl_is_yuv420(enum spl_pixel_format format) return false; } +static bool spl_is_yuv422(enum spl_pixel_format format) +{ + if ((format >= SPL_PIXEL_FORMAT_422BPP8) && + (format <= SPL_PIXEL_FORMAT_422BPP12)) + return true; + + return false; +} + static bool spl_is_rgb8(enum spl_pixel_format format) { if (format == SPL_PIXEL_FORMAT_ARGB8888) @@ -468,6 +476,12 @@ static void spl_calculate_scaling_ratios(struct spl_in *spl_in, if (spl_is_yuv420(spl_in->basic_in.format)) { spl_scratch->scl_data.ratios.horz_c.value /= 2; spl_scratch->scl_data.ratios.vert_c.value /= 2; + } else if (spl_is_yuv422(spl_in->basic_in.format)) { + if (spl_in->basic_in.rotation == SPL_ROTATION_ANGLE_90 || + spl_in->basic_in.rotation == SPL_ROTATION_ANGLE_270) + spl_scratch->scl_data.ratios.vert_c.value /= 2; + else + spl_scratch->scl_data.ratios.horz_c.value /= 2; } spl_scratch->scl_data.ratios.horz = spl_fixpt_truncate( spl_scratch->scl_data.ratios.horz, 19); @@ -612,7 +626,8 @@ static void spl_calculate_inits_and_viewports(struct spl_in *spl_in, struct spl_rect recout_clip_in_recout_dst; struct spl_rect overlap_in_active_timing; struct spl_rect odm_slice = calculate_odm_slice_in_timing_active(spl_in); - int vpc_div = spl_is_subsampled_format(spl_in->basic_in.format) ? 2 : 1; + int vp_hc_div = spl_is_subsampled_format(spl_in->basic_in.format) ? 2 : 1; + int vp_vc_div = spl_is_yuv420(spl_in->basic_in.format) ? 2 : 1; bool orthogonal_rotation, flip_vert_scan_dir, flip_horz_scan_dir; struct spl_fixed31_32 init_adj_h = spl_fixpt_zero; struct spl_fixed31_32 init_adj_v = spl_fixpt_zero; @@ -667,6 +682,7 @@ static void spl_calculate_inits_and_viewports(struct spl_in *spl_in, if (orthogonal_rotation) { spl_swap(src.width, src.height); spl_swap(flip_vert_scan_dir, flip_horz_scan_dir); + spl_swap(vp_hc_div, vp_vc_div); spl_swap(init_adj_h, init_adj_v); } @@ -685,7 +701,7 @@ static void spl_calculate_inits_and_viewports(struct spl_in *spl_in, flip_horz_scan_dir, recout_clip_in_recout_dst.x, spl_scratch->scl_data.recout.width, - src.width / vpc_div, + src.width / vp_hc_div, spl_scratch->scl_data.taps.h_taps_c, spl_scratch->scl_data.ratios.horz_c, init_adj_h, @@ -707,7 +723,7 @@ static void spl_calculate_inits_and_viewports(struct spl_in *spl_in, flip_vert_scan_dir, recout_clip_in_recout_dst.y, spl_scratch->scl_data.recout.height, - src.height / vpc_div, + src.height / vp_vc_div, spl_scratch->scl_data.taps.v_taps_c, spl_scratch->scl_data.ratios.vert_c, init_adj_v, @@ -719,12 +735,13 @@ static void spl_calculate_inits_and_viewports(struct spl_in *spl_in, spl_swap(spl_scratch->scl_data.viewport.width, spl_scratch->scl_data.viewport.height); spl_swap(spl_scratch->scl_data.viewport_c.x, spl_scratch->scl_data.viewport_c.y); spl_swap(spl_scratch->scl_data.viewport_c.width, spl_scratch->scl_data.viewport_c.height); + spl_swap(vp_hc_div, vp_vc_div); } spl_scratch->scl_data.viewport.x += src.x; spl_scratch->scl_data.viewport.y += src.y; - SPL_ASSERT(src.x % vpc_div == 0 && src.y % vpc_div == 0); - spl_scratch->scl_data.viewport_c.x += src.x / vpc_div; - spl_scratch->scl_data.viewport_c.y += src.y / vpc_div; + SPL_ASSERT(src.x % vp_hc_div == 0 && src.y % vp_vc_div == 0); + spl_scratch->scl_data.viewport_c.x += src.x / vp_hc_div; + spl_scratch->scl_data.viewport_c.y += src.y / vp_vc_div; } static void spl_handle_3d_recout(struct spl_in *spl_in, struct spl_rect *recout) @@ -760,6 +777,7 @@ static enum scl_mode spl_get_dscl_mode(const struct spl_in *spl_in, const struct spl_scaler_data *data, bool enable_isharp, bool enable_easf) { + (void)enable_easf; const long long one = spl_fixpt_one.value; enum spl_pixel_format pixel_format = spl_in->basic_in.format; @@ -894,7 +912,8 @@ static bool spl_get_isharp_en(struct spl_in *spl_in, static void spl_get_taps_non_adaptive_scaler( struct spl_scratch *spl_scratch, const struct spl_taps *in_taps, - bool is_subsampled) + bool is_horz_subsampled, + bool is_vert_subsampled) { bool check_max_downscale = false; @@ -959,11 +978,10 @@ static void spl_get_taps_non_adaptive_scaler( spl_scratch->scl_data.taps.h_taps = 1; if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert)) spl_scratch->scl_data.taps.v_taps = 1; - if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.horz_c) && !is_subsampled) + if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.horz_c) && !is_horz_subsampled) spl_scratch->scl_data.taps.h_taps_c = 1; - if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert_c) && !is_subsampled) + if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert_c) && !is_vert_subsampled) spl_scratch->scl_data.taps.v_taps_c = 1; - } /* Calculate optimal number of taps */ @@ -977,12 +995,13 @@ static bool spl_get_optimal_number_of_taps( unsigned int min_taps_y, min_taps_c; enum lb_memory_config lb_config; bool skip_easf = false; - bool is_subsampled = spl_is_subsampled_format(spl_in->basic_in.format); + bool is_horz_subsampled = spl_is_subsampled_format(spl_in->basic_in.format); + bool is_vert_subsampled = spl_is_yuv420(spl_in->basic_in.format); if (spl_scratch->scl_data.viewport.width > spl_scratch->scl_data.h_active && max_downscale_src_width != 0 && spl_scratch->scl_data.viewport.width > max_downscale_src_width) { - spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_subsampled); + spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_horz_subsampled, is_vert_subsampled); *enable_easf_v = false; *enable_easf_h = false; *enable_isharp = false; @@ -991,7 +1010,7 @@ static bool spl_get_optimal_number_of_taps( /* Disable adaptive scaler and sharpener when integer scaling is enabled */ if (spl_in->scaling_quality.integer_scaling) { - spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_subsampled); + spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_horz_subsampled, is_vert_subsampled); *enable_easf_v = false; *enable_easf_h = false; *enable_isharp = false; @@ -1007,15 +1026,15 @@ static bool spl_get_optimal_number_of_taps( * taps = 4 for upscaling */ if (skip_easf) { - spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_subsampled); + spl_get_taps_non_adaptive_scaler(spl_scratch, in_taps, is_horz_subsampled, is_vert_subsampled); } else { - if (spl_is_video_format(spl_in->basic_in.format)) { + if (spl_is_subsampled_format(spl_in->basic_in.format)) { spl_scratch->scl_data.taps.h_taps = 6; spl_scratch->scl_data.taps.v_taps = 6; spl_scratch->scl_data.taps.h_taps_c = 4; spl_scratch->scl_data.taps.v_taps_c = 4; - } else { /* RGB */ + } else { /* RGB / YUV444 */ spl_scratch->scl_data.taps.h_taps = 6; spl_scratch->scl_data.taps.v_taps = 6; spl_scratch->scl_data.taps.h_taps_c = 6; @@ -1149,10 +1168,10 @@ static bool spl_get_optimal_number_of_taps( (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert))) { spl_scratch->scl_data.taps.h_taps = 1; spl_scratch->scl_data.taps.v_taps = 1; - if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.horz_c) && !is_subsampled) + if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.horz_c) && !is_horz_subsampled) spl_scratch->scl_data.taps.h_taps_c = 1; - if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert_c) && !is_subsampled) + if (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert_c) && !is_vert_subsampled) spl_scratch->scl_data.taps.v_taps_c = 1; *enable_easf_v = false; @@ -1166,11 +1185,11 @@ static bool spl_get_optimal_number_of_taps( (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert))) spl_scratch->scl_data.taps.v_taps = 1; - if ((!*enable_easf_h) && !is_subsampled && + if ((!*enable_easf_h) && !is_horz_subsampled && (IDENTITY_RATIO(spl_scratch->scl_data.ratios.horz_c))) spl_scratch->scl_data.taps.h_taps_c = 1; - if ((!*enable_easf_v) && !is_subsampled && + if ((!*enable_easf_v) && !is_vert_subsampled && (IDENTITY_RATIO(spl_scratch->scl_data.ratios.vert_c))) spl_scratch->scl_data.taps.v_taps_c = 1; @@ -1693,6 +1712,7 @@ static void spl_set_isharp_data(struct dscl_prog_data *dscl_prog_data, const struct spl_scaler_data *data, struct spl_fixed31_32 ratio, enum system_setup setup, enum scale_to_sharpness_policy scale_to_sharpness_policy) { + (void)format; /* Turn off sharpener if not required */ if (!enable_isharp) { dscl_prog_data->isharp_en = 0; @@ -1823,6 +1843,144 @@ static void spl_set_isharp_data(struct dscl_prog_data *dscl_prog_data, SPL_NAMESPACE(spl_set_blur_scale_data(dscl_prog_data, data)); } +static void determine_upsp_values(struct spl_in *spl_in, struct dscl_prog_data *dscl_prog_data) +{ + dscl_prog_data->upsp_mode = spl_in->upsp_mode; + + if (dscl_prog_data->upsp_mode == UPSP_BYPASS) { //Set all UPSP register fields to 0 if bypass + dscl_prog_data->upsp_v_num_taps = UPSP_2_TAPS; + dscl_prog_data->upsp_h_num_taps = UPSP_2_TAPS; + dscl_prog_data->upsp_boundary_mode = UPSP_BOUNDARY_EDGE; + dscl_prog_data->upsp_v_init_int = 0x0; + dscl_prog_data->upsp_v_init_frac = 0x0; + dscl_prog_data->upsp_v_coef_tap0_p0 = 0x0; + dscl_prog_data->upsp_v_coef_tap1_p0 = 0x0; + dscl_prog_data->upsp_v_coef_tap2_p0 = 0x0; + dscl_prog_data->upsp_v_coef_tap3_p0 = 0x0; + dscl_prog_data->upsp_v_coef_tap0_p1 = 0x0; + dscl_prog_data->upsp_v_coef_tap1_p1 = 0x0; + dscl_prog_data->upsp_v_coef_tap2_p1 = 0x0; + dscl_prog_data->upsp_v_coef_tap3_p1 = 0x0; + dscl_prog_data->upsp_h_init_int = 0x0; + dscl_prog_data->upsp_h_init_frac = 0x0; + dscl_prog_data->upsp_h_coef_tap0_p0 = 0x0; + dscl_prog_data->upsp_h_coef_tap1_p0 = 0x0; + dscl_prog_data->upsp_h_coef_tap2_p0 = 0x0; + dscl_prog_data->upsp_h_coef_tap3_p0 = 0x0; + dscl_prog_data->upsp_h_coef_tap0_p1 = 0x0; + dscl_prog_data->upsp_h_coef_tap1_p1 = 0x0; + dscl_prog_data->upsp_h_coef_tap2_p1 = 0x0; + dscl_prog_data->upsp_h_coef_tap3_p1 = 0x0; + dscl_prog_data->upsp_clamp_max = 0x0; + dscl_prog_data->upsp_clamp_min = 0x0; + } else { + dscl_prog_data->upsp_v_num_taps = UPSP_4_TAPS; + dscl_prog_data->upsp_h_num_taps = UPSP_4_TAPS; + dscl_prog_data->upsp_boundary_mode = UPSP_BOUNDARY_EDGE; + dscl_prog_data->upsp_clamp_max = 0xFFF;//4095 + dscl_prog_data->upsp_clamp_min = 0x0; + + if (spl_in->basic_in.cositing == CHROMA_COSITING_TOPLEFT) { //Vertical Subsampling: Co-sited + if (dscl_prog_data->upsp_v_num_taps == UPSP_4_TAPS) { + dscl_prog_data->upsp_v_init_int = 0x3; + dscl_prog_data->upsp_v_init_frac = 0x0; + dscl_prog_data->upsp_v_coef_tap0_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap1_p0 = 0x40; + dscl_prog_data->upsp_v_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap0_p1 = 0xFC; + dscl_prog_data->upsp_v_coef_tap1_p1 = 0x24; + dscl_prog_data->upsp_v_coef_tap2_p1 = 0x24; + dscl_prog_data->upsp_v_coef_tap3_p1 = 0xFC; + } else { //2 taps + dscl_prog_data->upsp_v_init_int = 0x2; + dscl_prog_data->upsp_v_init_frac = 0x0; + dscl_prog_data->upsp_v_coef_tap0_p0 = 0x40; + dscl_prog_data->upsp_v_coef_tap1_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap0_p1 = 0x20; + dscl_prog_data->upsp_v_coef_tap1_p1 = 0x20; + dscl_prog_data->upsp_v_coef_tap2_p1 = 0x00; + dscl_prog_data->upsp_v_coef_tap3_p1 = 0x00; + } + } else { //Vertical Subsampling: Interstitial + if (dscl_prog_data->upsp_v_num_taps == UPSP_4_TAPS) { + dscl_prog_data->upsp_v_init_int = 0x2; + dscl_prog_data->upsp_v_init_frac = 0x1; + dscl_prog_data->upsp_v_coef_tap0_p0 = 0xFB; + dscl_prog_data->upsp_v_coef_tap1_p0 = 0x2F; + dscl_prog_data->upsp_v_coef_tap2_p0 = 0x19; + dscl_prog_data->upsp_v_coef_tap3_p0 = 0xFD; + dscl_prog_data->upsp_v_coef_tap0_p1 = 0xFD; + dscl_prog_data->upsp_v_coef_tap1_p1 = 0x19; + dscl_prog_data->upsp_v_coef_tap2_p1 = 0x2F; + dscl_prog_data->upsp_v_coef_tap3_p1 = 0xFB; + } else { //2 taps + dscl_prog_data->upsp_v_init_int = 0x1; + dscl_prog_data->upsp_v_init_frac = 0x1; + dscl_prog_data->upsp_v_coef_tap0_p0 = 0x28; + dscl_prog_data->upsp_v_coef_tap1_p0 = 0x18; + dscl_prog_data->upsp_v_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_v_coef_tap0_p1 = 0x18; + dscl_prog_data->upsp_v_coef_tap1_p1 = 0x28; + dscl_prog_data->upsp_v_coef_tap2_p1 = 0x00; + dscl_prog_data->upsp_v_coef_tap3_p1 = 0x00; + } + } + if (spl_in->basic_in.cositing == CHROMA_COSITING_LEFT || spl_in->basic_in.cositing == CHROMA_COSITING_TOPLEFT) { //Horizontal Subsampling: Co-sited + if (dscl_prog_data->upsp_h_num_taps == UPSP_4_TAPS) { + dscl_prog_data->upsp_h_init_int = 0x3; + dscl_prog_data->upsp_h_init_frac = 0x0; + dscl_prog_data->upsp_h_coef_tap0_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap1_p0 = 0x40; + dscl_prog_data->upsp_h_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap0_p1 = 0xFC; + dscl_prog_data->upsp_h_coef_tap1_p1 = 0x24; + dscl_prog_data->upsp_h_coef_tap2_p1 = 0x24; + dscl_prog_data->upsp_h_coef_tap3_p1 = 0xFC; + } else { //2 taps + dscl_prog_data->upsp_h_init_int = 0x2; + dscl_prog_data->upsp_h_init_frac = 0x0; + dscl_prog_data->upsp_h_coef_tap0_p0 = 0x40; + dscl_prog_data->upsp_h_coef_tap1_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap0_p1 = 0x20; + dscl_prog_data->upsp_h_coef_tap1_p1 = 0x20; + dscl_prog_data->upsp_h_coef_tap2_p1 = 0x00; + dscl_prog_data->upsp_h_coef_tap3_p1 = 0x00; + } + } else { //Horizontal Subsampling: Interstitial + if (dscl_prog_data->upsp_h_num_taps == UPSP_4_TAPS) { + dscl_prog_data->upsp_h_init_int = 0x2; + dscl_prog_data->upsp_h_init_frac = 0x1; + dscl_prog_data->upsp_h_coef_tap0_p0 = 0xFB; + dscl_prog_data->upsp_h_coef_tap1_p0 = 0x2F; + dscl_prog_data->upsp_h_coef_tap2_p0 = 0x19; + dscl_prog_data->upsp_h_coef_tap3_p0 = 0xFD; + dscl_prog_data->upsp_h_coef_tap0_p1 = 0xFD; + dscl_prog_data->upsp_h_coef_tap1_p1 = 0x19; + dscl_prog_data->upsp_h_coef_tap2_p1 = 0x2F; + dscl_prog_data->upsp_h_coef_tap3_p1 = 0xFB; + } else { //2 taps + dscl_prog_data->upsp_h_init_int = 0x1; + dscl_prog_data->upsp_h_init_frac = 0x1; + dscl_prog_data->upsp_h_coef_tap0_p0 = 0x28; + dscl_prog_data->upsp_h_coef_tap1_p0 = 0x18; + dscl_prog_data->upsp_h_coef_tap2_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap3_p0 = 0x00; + dscl_prog_data->upsp_h_coef_tap0_p1 = 0x18; + dscl_prog_data->upsp_h_coef_tap1_p1 = 0x28; + dscl_prog_data->upsp_h_coef_tap2_p1 = 0x00; + dscl_prog_data->upsp_h_coef_tap3_p1 = 0x00; + } + } + } +} + /* Calculate recout, scaling ratio, and viewport, then get optimal number of taps */ static bool spl_calculate_number_of_taps(struct spl_in *spl_in, struct spl_scratch *spl_scratch, struct spl_out *spl_out, bool *enable_easf_v, bool *enable_easf_h, bool *enable_isharp) @@ -1865,6 +2023,8 @@ bool SPL_NAMESPACE(spl_calculate_scaler_params(struct spl_in *spl_in, struct spl bool enable_isharp = false; const struct spl_scaler_data *data = &spl_scratch.scl_data; + determine_upsp_values(spl_in, spl_out->dscl_prog_data); + res = spl_calculate_number_of_taps(spl_in, &spl_scratch, spl_out, &enable_easf_v, &enable_easf_h, &enable_isharp); diff --git a/drivers/gpu/drm/amd/display/dc/sspl/dc_spl_types.h b/drivers/gpu/drm/amd/display/dc/sspl/dc_spl_types.h index 20e4e52a77ac..b24f1ef3fe17 100644 --- a/drivers/gpu/drm/amd/display/dc/sspl/dc_spl_types.h +++ b/drivers/gpu/drm/amd/display/dc/sspl/dc_spl_types.h @@ -62,17 +62,21 @@ enum spl_pixel_format { /*video*/ SPL_PIXEL_FORMAT_420BPP8, SPL_PIXEL_FORMAT_420BPP10, + SPL_PIXEL_FORMAT_422BPP8, + SPL_PIXEL_FORMAT_422BPP10, + SPL_PIXEL_FORMAT_422BPP12, + SPL_PIXEL_FORMAT_444BPP8, + SPL_PIXEL_FORMAT_444BPP10, /*end of pixel format definition*/ SPL_PIXEL_FORMAT_GRPH_BEGIN = SPL_PIXEL_FORMAT_INDEX8, SPL_PIXEL_FORMAT_GRPH_END = SPL_PIXEL_FORMAT_FP16, SPL_PIXEL_FORMAT_SUBSAMPLED_BEGIN = SPL_PIXEL_FORMAT_420BPP8, - SPL_PIXEL_FORMAT_SUBSAMPLED_END = SPL_PIXEL_FORMAT_420BPP10, + SPL_PIXEL_FORMAT_SUBSAMPLED_END = SPL_PIXEL_FORMAT_422BPP12, SPL_PIXEL_FORMAT_VIDEO_BEGIN = SPL_PIXEL_FORMAT_420BPP8, - SPL_PIXEL_FORMAT_VIDEO_END = SPL_PIXEL_FORMAT_420BPP10, + SPL_PIXEL_FORMAT_VIDEO_END = SPL_PIXEL_FORMAT_444BPP10, SPL_PIXEL_FORMAT_INVALID, SPL_PIXEL_FORMAT_UNKNOWN }; - enum lb_memory_config { /* Enable all 3 pieces of memory */ LB_MEMORY_CONFIG_0 = 0, @@ -88,7 +92,6 @@ enum lb_memory_config { */ LB_MEMORY_CONFIG_3 = 3 }; - /* Rotation angle */ enum spl_rotation_angle { SPL_ROTATION_ANGLE_0 = 0, @@ -128,6 +131,23 @@ enum chroma_cositing { CHROMA_COSITING_COUNT }; +enum upsp_mode { + UPSP_BYPASS = 0, + UPSP_HORIZONTAL_UPSAMPLING_ONLY, + UPSP_VERTICAL_UPSAMPLING_ONLY, + UPSP_HORIZONTAL_VERTICAL_UPSAMPLING +}; + +enum upsp_num_taps { + UPSP_2_TAPS, + UPSP_4_TAPS +}; + +enum upsp_boundary_mode { + UPSP_BOUNDARY_EDGE, //Replace out of bound samples with the edge samples + UPSP_BOUNDARY_BLACK //Replace out of bound samples with black as 12bpc(0x800) +}; + // Scratch space for calculating scaler params struct spl_scaler_data { int h_active; @@ -253,7 +273,7 @@ enum isharp_en { #define ISHARP_LUT_TABLE_SIZE 32 // Below struct holds values that can be directly used to program // hardware registers. No conversion/clamping is required -struct dscl_prog_data { +struct dscl_prog_data { struct spl_rect recout; // RECOUT - set based on scl_data.recout struct mpc_size mpc_size; uint32_t dscl_mode; @@ -395,6 +415,33 @@ struct dscl_prog_data { uint32_t easf_matrix_c1; uint32_t easf_matrix_c2; uint32_t easf_matrix_c3; + // UPSP registers + uint32_t upsp_mode;//UPSP_MODE + uint32_t upsp_v_num_taps; + uint32_t upsp_v_init_int; + uint32_t upsp_v_init_frac; + uint32_t upsp_h_num_taps; + uint32_t upsp_h_init_int; + uint32_t upsp_h_init_frac; + uint32_t upsp_boundary_mode; + uint32_t upsp_v_coef_tap0_p0;//UPSP_V_COEF_P0 + uint32_t upsp_v_coef_tap1_p0; + uint32_t upsp_v_coef_tap2_p0; + uint32_t upsp_v_coef_tap3_p0; + uint32_t upsp_v_coef_tap0_p1;//UPSP_V_COEF_P1 + uint32_t upsp_v_coef_tap1_p1; + uint32_t upsp_v_coef_tap2_p1; + uint32_t upsp_v_coef_tap3_p1; + uint32_t upsp_h_coef_tap0_p0;//UPSP_H_COEF_P0 + uint32_t upsp_h_coef_tap1_p0; + uint32_t upsp_h_coef_tap2_p0; + uint32_t upsp_h_coef_tap3_p0; + uint32_t upsp_h_coef_tap0_p1;//UPSP_H_COEF_P1 + uint32_t upsp_h_coef_tap1_p1; + uint32_t upsp_h_coef_tap2_p1; + uint32_t upsp_h_coef_tap3_p1; + uint32_t upsp_clamp_max;//UPSP_CLAMP + uint32_t upsp_clamp_min; // iSharp uint32_t isharp_en; // ISHARP_EN struct isharp_noise_det isharp_noise_det; // ISHARP_NOISEDET @@ -554,6 +601,7 @@ struct spl_in { int min_viewport_size; int sdr_white_level_nits; enum sharpen_policy sharpen_policy; + enum upsp_mode upsp_mode; }; // end of SPL inputs -- cgit v1.2.3 From c3349ee16f1e8e4c537cf5ef3542440b306d01fa Mon Sep 17 00:00:00 2001 From: Taimur Hassan Date: Sun, 12 Jul 2026 00:15:00 -0500 Subject: drm/amd/display: Promote DC to 3.2.390 This DC patchset brings improvements in multiple areas. In summary, we have: * Unified dc_update_state commit interface with dc_probe model, dc_state_get_status, and perfmon BLS sequence * DCN42 enhancements including mcache programming, PMFW DF C-state client, and DCCG clocking fix * DML2.1 writeback validation plus MCIF ARB and watermark/latency updates * Color management refactor to dc_plane_cm with plane/CRTC colorop test coverage * Greatly enhanced KUnit coverage across amdgpu_dm, connector, CRC, DMUB, and color management * HDMI AV mute timing, DP link training logging, and Apple Studio Display fixes * Code cleanup including DCE trim from DCN-only builds and HWSS refactors Acked-by: Tom Chung Signed-off-by: Taimur Hassan Signed-off-by: Wayne Lin Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index fcaa17b9ab1a..75c06a1752b5 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -66,7 +66,7 @@ struct dcn_dsc_reg_state; struct dcn_optc_reg_state; struct dcn_dccg_reg_state; -#define DC_VER "3.2.389" +#define DC_VER "3.2.390" /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC -- cgit v1.2.3 From 66f46209fd5ea6f7944f9f9d527f7ed46b6206b9 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Mon, 20 Jul 2026 08:41:09 +0530 Subject: drm/amdgpu: Drop vm_manager PASID to VM mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VM lookup users now resolve DRM PASIDs through the global PASID xarray: PASID -> fpriv -> VM The per-device vm_manager.pasids xarray is no longer needed. Remove PASID registration and removal from the VM init/fini paths, drop vm_manager PASID initialization/cleanup, and remove the xarray from struct amdgpu_vm_manager. The PASID is allocated only after amdgpu_vm_init() completes, so the initializer no longer consumes or assigns a PASID. Remove the now-unused argument while keeping vm->pasid as per-VM state for TLB flushes and other hardware programming paths. v6: - Remove the now-unused PASID argument from amdgpu_vm_init(). - Remove the related vm->pasid assignment and error-path reset from VM init. - Keep vm->pasid in struct amdgpu_vm for existing hardware users. Cc: Alex Deucher Suggested-by: Christian König Signed-off-by: Srinivasan Shanmugam Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 27 +-------------------------- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h | 6 +----- 3 files changed, 3 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 46eabdda49f3..1e6ad59b74e0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -1494,7 +1494,7 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) amdgpu_debugfs_vm_init(file_priv); - r = amdgpu_vm_init(adev, &fpriv->vm, fpriv->xcp_id, 0); + r = amdgpu_vm_init(adev, &fpriv->vm, fpriv->xcp_id); if (r) goto error_pasid; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 146902ffc875..aac8ace9d7a6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2556,7 +2556,6 @@ void amdgpu_vm_set_task_info(struct amdgpu_vm *vm) * @adev: amdgpu_device pointer * @vm: requested vm * @xcp_id: GPU partition selection id - * @pasid: the pasid the VM is using on this GPU * * Init @vm fields. * @@ -2564,7 +2563,7 @@ void amdgpu_vm_set_task_info(struct amdgpu_vm *vm) * 0 for success, error for failure. */ int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm, - int32_t xcp_id, uint32_t pasid) + int32_t xcp_id) { struct amdgpu_bo *root_bo; struct amdgpu_bo_vm *root; @@ -2639,26 +2638,12 @@ int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm, if (r) dev_dbg(adev->dev, "Failed to create task info for VM\n"); - /* Store new PASID in XArray (if non-zero) */ - if (pasid != 0) { - r = xa_err(xa_store_irq(&adev->vm_manager.pasids, pasid, vm, GFP_KERNEL)); - if (r < 0) - goto error_free_root; - - vm->pasid = pasid; - } - amdgpu_bo_unreserve(vm->root.bo); amdgpu_bo_unref(&root_bo); return 0; error_free_root: - /* If PASID was partially set, erase it from XArray before failing */ - if (vm->pasid != 0) { - xa_erase_irq(&adev->vm_manager.pasids, vm->pasid); - vm->pasid = 0; - } amdgpu_vm_pt_free_root(adev, vm); amdgpu_bo_unreserve(vm->root.bo); amdgpu_bo_unref(&root_bo); @@ -2765,11 +2750,6 @@ void amdgpu_vm_fini(struct amdgpu_device *adev, struct amdgpu_vm *vm) root = amdgpu_bo_ref(vm->root.bo); amdgpu_bo_reserve(root, true); - /* Remove PASID mapping before destroying VM */ - if (vm->pasid != 0) { - xa_erase_irq(&adev->vm_manager.pasids, vm->pasid); - vm->pasid = 0; - } dma_fence_wait(vm->last_unlocked, false); dma_fence_put(vm->last_unlocked); dma_fence_wait(vm->last_tlb_flush, false); @@ -2865,8 +2845,6 @@ void amdgpu_vm_manager_init(struct amdgpu_device *adev) #else adev->vm_manager.vm_update_mode = 0; #endif - - xa_init_flags(&adev->vm_manager.pasids, XA_FLAGS_LOCK_IRQ); } /** @@ -2878,9 +2856,6 @@ void amdgpu_vm_manager_init(struct amdgpu_device *adev) */ void amdgpu_vm_manager_fini(struct amdgpu_device *adev) { - WARN_ON(!xa_empty(&adev->vm_manager.pasids)); - xa_destroy(&adev->vm_manager.pasids); - amdgpu_vmid_mgr_fini(adev); amdgpu_pasid_mgr_cleanup(); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h index 5822836fa4a3..2f8234560764 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h @@ -481,10 +481,6 @@ struct amdgpu_vm_manager { */ int vm_update_mode; - /* PASID to VM mapping, will be used in interrupt context to - * look up VM of a page fault - */ - struct xarray pasids; /* Global registration of recent page fault information */ struct amdgpu_vm_fault_info fault_info; }; @@ -502,7 +498,7 @@ void amdgpu_vm_manager_init(struct amdgpu_device *adev); void amdgpu_vm_manager_fini(struct amdgpu_device *adev); long amdgpu_vm_wait_idle(struct amdgpu_vm *vm, long timeout); -int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm, int32_t xcp_id, uint32_t pasid); +int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm, int32_t xcp_id); int amdgpu_vm_make_compute(struct amdgpu_device *adev, struct amdgpu_vm *vm); void amdgpu_vm_fini(struct amdgpu_device *adev, struct amdgpu_vm *vm); int amdgpu_vm_lock_pd(struct amdgpu_vm *vm, struct drm_exec *exec, -- cgit v1.2.3 From a107f74c38edbb80d6ab64dcaeeb292c14e9779f Mon Sep 17 00:00:00 2001 From: Vladimir Marioukhine Date: Mon, 20 Jul 2026 11:53:30 -0400 Subject: drm/amdkfd: fix QID bit leak in pqm_create_queue() When MES is enabled and amdgpu_amdkfd_alloc_kernel_mem() fails during the first queue creation for a process, pqm_create_queue() returns early via 'return retval' without going through the err_create_queue cleanup label. This means clear_bit(*qid, pqm->queue_slot_bitmap) is never called, leaving the reserved QID bit permanently set in queue_slot_bitmap. Over time this leaks QID slots, potentially exhausting all available queue slots. Fix this by replacing 'return retval' with 'goto err_allocate_pqn' so that clear_bit() is always called on the error path without touching the uninitialized pqn pointer. AILIKFD-813 Reported-by: Deucher, Alexander Signed-off-by: Vladimir Marioukhine Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c index 97e77a5a35e5..8054e4fe0381 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c @@ -383,7 +383,7 @@ int pqm_create_queue(struct process_queue_manager *pqm, false); if (retval) { dev_err(dev->adev->dev, "failed to allocate process context bo\n"); - return retval; + goto err_allocate_pqn; } memset(pdd->proc_ctx_cpu_ptr, 0, AMDGPU_MES_PROC_CTX_SIZE); } -- cgit v1.2.3 From 764f241ad227bb942e5b0b8b4d9898f1a4175605 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 9 Jul 2026 16:33:11 -0600 Subject: drm/amdgpu: Fix __rcu fence pointer accesses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Building for ARCH=um with W=1 C=1 makes sparse report "incompatible types in comparison expression (different address spaces)" warnings in the KFD code, exposed after UML builds were enabled: - amdgpu_amdkfd_fence.c compares the __rcu-annotated dma_fence.ops pointer directly in to_amdgpu_amdkfd_fence(). - amdgpu_amdkfd_gpuvm.c compares the __rcu eviction fence pointer directly in amdgpu_amdkfd_gpuvm_restore_process_bos(). Fixes: af3f2f5db265 ("drm/amdgpu: Remove UML build exclusion from Kconfig") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607091659.SHEscT0c-lkp@intel.com/ Cc: Harry Wentland Assisted-by: Copilot:Claude-Opus-4.8 Signed-off-by: Alex Hung Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c index 6a364357522b..b0299d861903 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c @@ -92,7 +92,7 @@ struct amdgpu_amdkfd_fence *to_amdgpu_amdkfd_fence(struct dma_fence *f) return NULL; fence = container_of(f, struct amdgpu_amdkfd_fence, base); - if (f->ops == &amdkfd_fence_ops) + if (rcu_access_pointer(f->ops) == &amdkfd_fence_ops) return fence; return NULL; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c index 20831dbebc31..1217fb516807 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c @@ -3104,7 +3104,7 @@ int amdgpu_amdkfd_gpuvm_restore_process_bos(void *info, struct dma_fence __rcu * process_info->eviction_fence = new_fence; replace_eviction_fence(ef, dma_fence_get(&new_fence->base)); } else { - WARN_ONCE(*ef != &process_info->eviction_fence->base, + WARN_ONCE(rcu_access_pointer(*ef) != &process_info->eviction_fence->base, "KFD eviction fence doesn't match KGD process_info"); } -- cgit v1.2.3 From 1d8cfeb69daa863a70134b8ed6df8055c418a5b0 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 9 Jul 2026 16:33:12 -0600 Subject: drm/amdgpu: Pack nested ucode_info struct Building for ARCH=um with W=1 C=1 makes the "amd_sriov_msg_vf2pf_info must be 1 KB" static assertion in amdgv_sriovmsg.h fail under sparse, exposed after UML builds were enabled. Sparse does not honor #pragma pack(push, 1) for the nested ucode_info struct, so it sizes each element as 8 bytes instead of 5 and computes the surrounding structure as larger than 1 KB. The compilers get this right via the enclosing pragma, but the annotation should be explicit. Fixes: af3f2f5db265 ("drm/amdgpu: Remove UML build exclusion from Kconfig") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607091659.SHEscT0c-lkp@intel.com/ Cc: Harry Wentland Assisted-by: Copilot:Claude-Opus-4.8 Signed-off-by: Alex Hung Reviewed-by: Harry Wentland Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h b/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h index d80f01c0e754..b02561f41b58 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h @@ -379,7 +379,7 @@ struct amd_sriov_msg_vf2pf_info { struct { uint8_t id; uint32_t version; - } ucode_info[AMD_SRIOV_MSG_RESERVE_UCODE]; + } __packed ucode_info[AMD_SRIOV_MSG_RESERVE_UCODE]; uint64_t dummy_page_addr; /* FB allocated for guest MES to record UQ info */ uint64_t mes_info_addr; -- cgit v1.2.3 From 459e61b53910be3c6dff4d97ae95bb6116ee65dd Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 10 Jul 2026 21:31:58 -0600 Subject: drm/amd/display: Fix writeback completion timing [WHY] The out fence was signalled on the first vblank after arming, before the DMA finished copying, and the old code worked around this with an mdelay() in the IRQ handler. [HOW] Hold a vblank reference while writeback is pending and signal the out fence on the second vblank instead of using mdelay(). Add amdgpu_dm_crtc_complete_writeback() to finish and clean up writeback from both the IRQ and teardown paths. This can be verified by running IGT's kms_writeback 20 times without timeout errors. Assisted-by: Copilot:Claude-Opus-4.8 Signed-off-by: Alex Hung Reviewed-by: Mario Limonciello (AMD) Reviewed-by: Harry Wentland Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h | 1 + drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 2 ++ .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 33 ++++++++++++---------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h index 8069fc41cc7f..7c784277396a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h @@ -509,6 +509,7 @@ struct amdgpu_crtc { struct drm_pending_vblank_event *event; bool wb_pending; + bool wb_frame_done; bool wb_enabled; struct drm_writeback_connector *wb_conn; }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 4b52ed3fb2bf..372df3146c19 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4688,6 +4688,7 @@ bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc) spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); pending = acrtc->wb_pending; acrtc->wb_pending = false; + acrtc->wb_frame_done = false; spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); if (!pending) @@ -5156,6 +5157,7 @@ static void dm_set_writeback(struct amdgpu_display_manager *dm, * cannot run its matching vblank_put before this get. */ WARN_ON(drm_crtc_vblank_get(&acrtc->base)); + acrtc->wb_frame_done = false; acrtc->wb_pending = true; } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 9c574849142b..9be63996b062 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1960,23 +1960,26 @@ static void dm_crtc_high_irq_handler(struct amdgpu_device *adev, bool is_dcn = amdgpu_ip_version(adev, DCE_HWIP, 0) != 0; if (acrtc->wb_conn && acrtc->wb_pending) { - struct dc_stream_state *stream = acrtc->dm_irq_params.stream; - unsigned int v_total, refresh_hz; - - v_total = stream->adjust.v_total_max ? - stream->adjust.v_total_max : stream->timing.v_total; - refresh_hz = div_u64((uint64_t) stream->timing.pix_clk_100hz * - 100LL, (v_total * stream->timing.h_total)); - mdelay(1000 / refresh_hz); - - /* - * Completion (signalling the out fence and releasing the vblank - * reference taken in dm_set_writeback()) is handled by the shared - * helper, which is also used by the teardown path. - */ - if (amdgpu_dm_crtc_complete_writeback(acrtc)) + if (acrtc->wb_frame_done) { + /* + * Second vblank: the DMA for the captured frame has + * had a full frame period to flush to memory. Signal + * the out fence now. + */ + amdgpu_dm_crtc_complete_writeback(acrtc); + } else { + /* + * First vblank after arming: the frame has been + * scanned out and the DMA is finishing. Disable + * writeback immediately to prevent the hardware from + * starting a new capture that would overwrite the + * buffer. Signal completion on the next vblank to + * ensure the DMA is fully flushed to memory. + */ dc_stream_fc_disable_writeback(adev->dm.dc, acrtc->dm_irq_params.stream, 0); + acrtc->wb_frame_done = true; + } } vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); -- cgit v1.2.3 From e6ed7d9c391a82d111b705c080c51f8edc059781 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Sun, 19 Jul 2026 23:06:27 +0530 Subject: drm/amdgpu: Add missing MES helper parameter descriptions Complete the kernel-doc comments for the MES RS64 memory, process context, and gang context helper functions by documenting the missing function parameters to keep the kernel-doc comments consistent with their function signatures. Fixes: 56f7ea845e7d ("drm/amdgpu: add RS64 local memory context array init/fini") Fixes: cf5f98609a03 ("drm/amdgpu: add mes process context alloc/free") Fixes: 5ab89b491f21 ("drm/amdgpu: add mes gang contex alloc/free helper") Cc: Prike Liang Cc: Michael Chen Signed-off-by: Srinivasan Shanmugam Reviewed-by: Michael Chen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 43 ++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index 1e3a23c81552..9af7c97af5be 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -952,9 +952,11 @@ int amdgpu_mes_rs64mem_init(struct amdgpu_mes *mes) return 0; } -/** - * amdgpu_mes_rs64mem_fini - tear down RS64 local memory management - */ + /** + * amdgpu_mes_rs64mem_fini - tear down RS64 local memory management + * + * @mes: MES instance + */ void amdgpu_mes_rs64mem_fini(struct amdgpu_mes *mes) { if (mes->ctx_array_size_bo) { @@ -1038,9 +1040,10 @@ int amdgpu_mes_rs64mem_setup_bitmaps(struct amdgpu_mes *mes) * amdgpu_mes_alloc_proc_ctx_index - allocate a process context slot * * @mes: MES instance + * @queue: Usermode queue receiving the allocated process context index * - * Returns 0 on success, -ENOSPC if all slots are used (caller should - * fall back to system memory path). + * Returns 0 on success, -ENOSPC if all slots are used, or + * -EOPNOTSUPP if RS64 local memory is unavailable. */ int amdgpu_mes_alloc_proc_ctx_index(struct amdgpu_mes *mes, struct amdgpu_usermode_queue *queue) @@ -1064,9 +1067,12 @@ int amdgpu_mes_alloc_proc_ctx_index(struct amdgpu_mes *mes, return 0; } -/** - * amdgpu_mes_free_proc_ctx_index - free a process context slot - */ + /** + * amdgpu_mes_free_proc_ctx_index - free a process context slot + * + * @mes: MES instance + * @queue: Usermode queue whose process context index is released + */ void amdgpu_mes_free_proc_ctx_index(struct amdgpu_mes *mes, struct amdgpu_usermode_queue *queue) { @@ -1080,9 +1086,15 @@ void amdgpu_mes_free_proc_ctx_index(struct amdgpu_mes *mes, amdgpu_mes_unlock(mes); } -/** - * amdgpu_mes_alloc_gang_ctx_index - allocate a gang context slot - */ + /** + * amdgpu_mes_alloc_gang_ctx_index - allocate a gang context slot + * + * @mes: MES instance + * @queue: Usermode queue receiving the allocated gang context index + * + * Returns 0 on success, -ENOSPC if all slots are used, or + * -EOPNOTSUPP if RS64 local memory is unavailable. + */ int amdgpu_mes_alloc_gang_ctx_index(struct amdgpu_mes *mes, struct amdgpu_usermode_queue *queue) { @@ -1105,9 +1117,12 @@ int amdgpu_mes_alloc_gang_ctx_index(struct amdgpu_mes *mes, return 0; } -/** - * amdgpu_mes_free_gang_ctx_index - free a gang context slot - */ + /** + * amdgpu_mes_free_gang_ctx_index - free a gang context slot + * + * @mes: MES instance + * @queue: Usermode queue whose gang context index is released + */ void amdgpu_mes_free_gang_ctx_index(struct amdgpu_mes *mes, struct amdgpu_usermode_queue *queue) { -- cgit v1.2.3 From 482e2cadea8c34ae4e733f269a640d6b04fc9262 Mon Sep 17 00:00:00 2001 From: Fares Soliman Date: Mon, 13 Jul 2026 09:18:41 -0400 Subject: drm/amdgpu: Update message IDs to PMFW to correctly gather GFXOFF residency logs Updates PPSMC_MSGs and set/get functions for gathering GFXOFF logs on Van Gogh. Logs are now gathered live rather than starting then stopping logging and reading an average value afterwards. This is in accordance to changes made in PMFW. In regards to messageID 0x52, the old interface uses a start/stop parameter, and the new one doesn't. The firmware is checked to determine which method to use. v2: added firmware guard to new interface, old interface kept as fallback Signed-off-by: Fares Soliman Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 4 +- drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h | 2 +- .../drm/amd/pm/swsmu/inc/pmfw_if/smu_v11_5_ppsmc.h | 6 ++- drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h | 4 +- drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c | 50 +++++++++++++++++----- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index deab64765388..a0eadbb5772b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -1320,8 +1320,8 @@ err: * @size: Number of bytes to read * @pos: Offset to seek to * - * Read the last residency value logged. It doesn't auto update, one needs to - * stop logging before getting the current value. + * Read a live GFXOFF residency sample from firmware. One needs to start logging + * before getting the current value. */ static ssize_t amdgpu_debugfs_gfxoff_residency_read(struct file *f, char __user *buf, size_t size, loff_t *pos) diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h index 7ea7c4a5279b..eb945f1805ed 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h @@ -1336,7 +1336,7 @@ struct pptable_funcs { u32 (*set_gfx_off_residency)(struct smu_context *smu, bool start); /** - * @get_gfx_off_residency: Average GFXOFF residency % during the logging interval + * @get_gfx_off_residency: Live GFXOFF residency percentage */ u32 (*get_gfx_off_residency)(struct smu_context *smu, uint32_t *residency); diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v11_5_ppsmc.h b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v11_5_ppsmc.h index 7471e2df2828..4206514765cd 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v11_5_ppsmc.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu_v11_5_ppsmc.h @@ -110,8 +110,10 @@ #define PPSMC_MSG_GetSlowPPTLimit 0x4C #define PPSMC_MSG_GetGfxOffStatus 0x50 #define PPSMC_MSG_GetGfxOffEntryCount 0x51 -#define PPSMC_MSG_LogGfxOffResidency 0x52 -#define PPSMC_Message_Count 0x53 +#define PPSMC_MSG_GfxOffResidencyLogReadSample 0x52 +#define PPSMC_MSG_StopGfxOffResidencyLogging 0x53 +#define PPSMC_MSG_StartGfxOffResidencyLogging 0x56 +#define PPSMC_Message_Count 0x57 //Argument for PPSMC_MSG_GfxDeviceDriverReset enum { diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h index 636ff90923d9..acf03838d49d 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_types.h @@ -252,7 +252,9 @@ __SMU_DUMMY_MAP(DriverMode2Reset), \ __SMU_DUMMY_MAP(GetGfxOffStatus), \ __SMU_DUMMY_MAP(GetGfxOffEntryCount), \ - __SMU_DUMMY_MAP(LogGfxOffResidency), \ + __SMU_DUMMY_MAP(StartGfxOffResidencyLogging), \ + __SMU_DUMMY_MAP(GfxOffResidencyLogReadSample), \ + __SMU_DUMMY_MAP(StopGfxOffResidencyLogging), \ __SMU_DUMMY_MAP(SetNumBadMemoryPagesRetired), \ __SMU_DUMMY_MAP(SetBadMemoryPagesRetiredFlagsPerChannel), \ __SMU_DUMMY_MAP(AllowGpo), \ diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c index 016a5c893fee..c54675b00a96 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c @@ -72,6 +72,12 @@ static const struct smu_feature_bits vangogh_dpm_features = { } }; +/* + * SMU support new GFXOFF residency log interface since version 4.63.62.00, + * use this to get live readings of GFXOFF residency + */ +#define SUPPORT_LIVE_RESIDENCY_MSG_VERSION 0x043f3e00 + static struct cmn2asic_msg_mapping vangogh_message_map[SMU_MSG_MAX_COUNT] = { MSG_MAP(TestMessage, PPSMC_MSG_TestMessage, 0), MSG_MAP(GetSmuVersion, PPSMC_MSG_GetSmuVersion, 0), @@ -142,7 +148,9 @@ static struct cmn2asic_msg_mapping vangogh_message_map[SMU_MSG_MAX_COUNT] = { MSG_MAP(GetSlowPPTLimit, PPSMC_MSG_GetSlowPPTLimit, 0), MSG_MAP(GetGfxOffStatus, PPSMC_MSG_GetGfxOffStatus, 0), MSG_MAP(GetGfxOffEntryCount, PPSMC_MSG_GetGfxOffEntryCount, 0), - MSG_MAP(LogGfxOffResidency, PPSMC_MSG_LogGfxOffResidency, 0), + MSG_MAP(StartGfxOffResidencyLogging, PPSMC_MSG_StartGfxOffResidencyLogging, 0), + MSG_MAP(GfxOffResidencyLogReadSample, PPSMC_MSG_GfxOffResidencyLogReadSample, 0), + MSG_MAP(StopGfxOffResidencyLogging, PPSMC_MSG_StopGfxOffResidencyLogging, 0), }; static struct cmn2asic_mapping vangogh_feature_mask_map[SMU_FEATURE_COUNT] = { @@ -2449,19 +2457,32 @@ static int vangogh_set_power_limit(struct smu_context *smu, static u32 vangogh_set_gfxoff_residency(struct smu_context *smu, bool start) { int ret = 0; - u32 residency; struct amdgpu_device *adev = smu->adev; if (!(adev->pm.pp_feature & PP_GFXOFF_MASK)) return 0; - ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_LogGfxOffResidency, - start, &residency); - if (ret) - return ret; + if (smu->smc_fw_version < SUPPORT_LIVE_RESIDENCY_MSG_VERSION) { + u32 residency; + + ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_GfxOffResidencyLogReadSample, + start, &residency); + if (ret) + return ret; - if (!start) - adev->gfx.gfx_off_residency = residency; + if (!start) + adev->gfx.gfx_off_residency = residency; + } else { + if (start) { + ret = smu_cmn_send_smc_msg(smu, SMU_MSG_StartGfxOffResidencyLogging, NULL); + if (ret) + return ret; + } else { + ret = smu_cmn_send_smc_msg(smu, SMU_MSG_StopGfxOffResidencyLogging, NULL); + if (ret) + return ret; + } + } return ret; } @@ -2478,11 +2499,20 @@ static u32 vangogh_set_gfxoff_residency(struct smu_context *smu, bool start) */ static u32 vangogh_get_gfxoff_residency(struct smu_context *smu, uint32_t *residency) { + int ret = 0; struct amdgpu_device *adev = smu->adev; - *residency = adev->gfx.gfx_off_residency; + if (!(adev->pm.pp_feature & PP_GFXOFF_MASK)) + return 0; + + if (smu->smc_fw_version < SUPPORT_LIVE_RESIDENCY_MSG_VERSION) { + *residency = adev->gfx.gfx_off_residency; + } else { + ret = smu_cmn_send_smc_msg(smu, SMU_MSG_GfxOffResidencyLogReadSample, + residency); + } - return 0; + return ret; } /** -- cgit v1.2.3 From b8e9449627b11d1eda89a150e346a73863209240 Mon Sep 17 00:00:00 2001 From: Tao Zhou Date: Mon, 6 Jul 2026 16:16:34 +0800 Subject: drm/amd/ras: send RMA event in RAS init stage Previously the event was only raised at runtime, also raise it during RAS init when threshold is exceeded and eeprom header is tagged bad, so we can get related cper at boot time. Signed-off-by: Tao Zhou Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c index 62d1a319c08c..3b1883a04179 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c @@ -1261,6 +1261,11 @@ int ras_eeprom_check_storage_status(struct ras_core_context *ras_core) } else { RAS_DEV_ERR(ras_core->dev, "RAS records:%d exceed threshold:%d", bad_page_count, control->record_threshold_count); + /* send the event when threshold is exceeded, and ignore the + * return value here + */ + ras_core_event_notify(ras_core, RAS_EVENT_ID__DEVICE_RMA, NULL); + if ((control->record_threshold_config == WARN_NONSTOP_OVER_THRESHOLD) || (control->record_threshold_config == NONSTOP_OVER_THRESHOLD)) { RAS_DEV_WARN(ras_core->dev, -- cgit v1.2.3 From 2e8e9963cd5c41aa14fd5316bf9ec92e7a0e3097 Mon Sep 17 00:00:00 2001 From: David Francis Date: Tue, 21 Jul 2026 09:30:07 -0400 Subject: drm/amdkfd: Handle invalid event type in CRIU event restore In kfd_criu_restore_event, there was no handling for the event priv data having an invalid event type. The priv data here is untrusted and can be invalid. In that case, fail with EINVAL. Signed-off-by: David Francis Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_events.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_events.c index f5fd78f0df7d..9e0f57cfa3bf 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.c @@ -452,6 +452,9 @@ int kfd_criu_restore_event(struct file *devkfd, ret = create_other_event(p, ev, &ev_priv->event_id); break; + default: + ret = -EINVAL; + break; } mutex_unlock(&p->event_mutex); -- cgit v1.2.3 From 3ee6561f8ae0ae3c80d04429361a6e06589693fc Mon Sep 17 00:00:00 2001 From: Kanala Ramalingeswara Reddy Date: Thu, 16 Jul 2026 22:25:52 +0530 Subject: drm/amdgpu: Update driver if header for SMU V15.0.5 Update smu v15.0.0 driver if header to be v15.0.5 compatible. Signed-off-by: Kanala Ramalingeswara Reddy Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher --- .../pm/swsmu/inc/pmfw_if/smu15_driver_if_v15_0_0.h | 38 ++++ drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h | 2 +- .../gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c | 220 ++++++++++++++++++++- 3 files changed, 253 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu15_driver_if_v15_0_0.h b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu15_driver_if_v15_0_0.h index 47ddf533b08f..a5d3e8d88860 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu15_driver_if_v15_0_0.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/pmfw_if/smu15_driver_if_v15_0_0.h @@ -144,6 +144,44 @@ typedef struct { uint32_t MaxGfxClk; } DpmClocks_t; +//Freq in MHz +//Voltage in milli volts with 2 fractional bits +typedef struct { + uint32_t DcfClocks[NUM_DCFCLK_DPM_LEVELS]; + uint32_t DispClocks[NUM_DISPCLK_DPM_LEVELS]; + uint32_t DppClocks[NUM_DPPCLK_DPM_LEVELS]; + uint32_t SocClocks[NUM_SOCCLK_DPM_LEVELS]; + + uint32_t VPEClocks[NUM_VPE_DPM_LEVELS]; + uint32_t FclkClocks_Freq[NUM_FCLK_DPM_LEVELS]; + uint32_t FclkClocks_Voltage[NUM_FCLK_DPM_LEVELS]; + uint32_t SocVoltage[NUM_SOC_VOLTAGE_LEVELS]; + MemPstateTable_t MemPstateTable[NUM_MEM_PSTATE_LEVELS]; + + uint8_t NumDcfClkLevelsEnabled; + uint8_t NumDispClkLevelsEnabled; //Applies to both Dispclk and Dppclk + uint8_t NumSocClkLevelsEnabled; + + uint8_t VpeClkLevelsEnabled; + + uint8_t NumMemPstatesEnabled; + uint8_t NumFclkLevelsEnabled; + uint8_t Spare1; + uint8_t Spare2; + + uint8_t Spare3; + uint8_t Spare4; + uint8_t Spare5[2]; + + uint32_t MinGfxClk; + uint32_t MaxGfxClk; + + uint32_t Spare6[8]; + uint32_t Spare7[8]; + uint32_t Spare8[8]; + uint32_t Spare9[8]; +} DpmClocks_t_v15_0_5; + typedef struct { uint16_t CoreFrequency[16]; //Target core frequency [MHz] uint16_t CorePower[16]; //CAC calculated core power [mW] diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h index 13723d45a7de..38e625cda97d 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h @@ -26,7 +26,7 @@ #include "amdgpu_smu.h" #define SMU15_DRIVER_IF_VERSION_INV 0xFFFFFFFF -#define SMU15_DRIVER_IF_VERSION_SMU_V15_0 0x7 +#define SMU15_DRIVER_IF_VERSION_SMU_V15_0 0x9 #define FEATURE_MASK(feature) (1ULL << feature) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c index bb8d09e73c7d..faa7d661f3bf 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c @@ -168,7 +168,7 @@ static int smu_v15_0_0_init_smc_tables(struct smu_context *smu) SMU_TABLE_INIT(tables, SMU_TABLE_WATERMARKS, sizeof(Watermarks_t), PAGE_SIZE, AMDGPU_GEM_DOMAIN_VRAM); - SMU_TABLE_INIT(tables, SMU_TABLE_DPMCLOCKS, sizeof(DpmClocks_t), + SMU_TABLE_INIT(tables, SMU_TABLE_DPMCLOCKS, sizeof(DpmClocks_t_v15_0_5), PAGE_SIZE, AMDGPU_GEM_DOMAIN_VRAM); SMU_TABLE_INIT(tables, SMU_TABLE_SMU_METRICS, sizeof(SmuMetrics_t), PAGE_SIZE, AMDGPU_GEM_DOMAIN_VRAM); @@ -178,7 +178,7 @@ static int smu_v15_0_0_init_smc_tables(struct smu_context *smu) goto err0_out; smu_table->metrics_time = 0; - smu_table->clocks_table = kzalloc_obj(DpmClocks_t); + smu_table->clocks_table = kzalloc_obj(DpmClocks_t_v15_0_5); if (!smu_table->clocks_table) goto err1_out; @@ -752,11 +752,48 @@ static int smu_v15_0_0_get_dpm_freq_by_index(struct smu_context *smu, return 0; } +static int smu_v15_0_5_get_dpm_freq_by_index(struct smu_context *smu, + enum smu_clk_type clk_type, + uint32_t dpm_level, + uint32_t *freq) +{ + DpmClocks_t_v15_0_5 *clk_table = smu->smu_table.clocks_table; + + if (!clk_table || clk_type >= SMU_CLK_COUNT) + return -EINVAL; + + switch (clk_type) { + case SMU_SOCCLK: + if (dpm_level >= clk_table->NumSocClkLevelsEnabled) + return -EINVAL; + *freq = clk_table->SocClocks[dpm_level]; + break; + case SMU_UCLK: + case SMU_MCLK: + if (dpm_level >= clk_table->NumMemPstatesEnabled) + return -EINVAL; + *freq = clk_table->MemPstateTable[dpm_level].MemClk; + break; + case SMU_FCLK: + if (dpm_level >= clk_table->NumFclkLevelsEnabled) + return -EINVAL; + *freq = clk_table->FclkClocks_Freq[dpm_level]; + break; + default: + return -EINVAL; + } + + return 0; +} + static int smu_v15_0_common_get_dpm_freq_by_index(struct smu_context *smu, enum smu_clk_type clk_type, uint32_t dpm_level, uint32_t *freq) { + if (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) == IP_VERSION(15, 0, 5)) + smu_v15_0_5_get_dpm_freq_by_index(smu, clk_type, dpm_level, freq); + else smu_v15_0_0_get_dpm_freq_by_index(smu, clk_type, dpm_level, freq); return 0; @@ -907,12 +944,116 @@ failed: return ret; } +static int smu_v15_0_5_get_dpm_ultimate_freq(struct smu_context *smu, + enum smu_clk_type clk_type, + uint32_t *min, + uint32_t *max) +{ + DpmClocks_t_v15_0_5 *clk_table = smu->smu_table.clocks_table; + uint32_t clock_limit; + uint32_t max_dpm_level, min_dpm_level; + int ret = 0; + + if (!smu_v15_0_0_clk_dpm_is_enabled(smu, clk_type)) { + switch (clk_type) { + case SMU_MCLK: + case SMU_UCLK: + clock_limit = smu->smu_table.boot_values.uclk; + break; + case SMU_FCLK: + clock_limit = smu->smu_table.boot_values.fclk; + break; + case SMU_GFXCLK: + case SMU_SCLK: + clock_limit = smu->smu_table.boot_values.gfxclk; + break; + case SMU_SOCCLK: + clock_limit = smu->smu_table.boot_values.socclk; + break; + default: + clock_limit = 0; + break; + } + + /* clock in Mhz unit */ + if (min) + *min = clock_limit / 100; + if (max) + *max = clock_limit / 100; + + return 0; + } + + if (max) { + switch (clk_type) { + case SMU_GFXCLK: + case SMU_SCLK: + *max = clk_table->MaxGfxClk; + break; + case SMU_MCLK: + case SMU_UCLK: + max_dpm_level = 0; + break; + case SMU_FCLK: + max_dpm_level = clk_table->NumFclkLevelsEnabled - 1; + break; + case SMU_SOCCLK: + max_dpm_level = clk_table->NumSocClkLevelsEnabled - 1; + break; + default: + ret = -EINVAL; + goto failed; + } + + if (clk_type != SMU_GFXCLK && clk_type != SMU_SCLK) { + ret = smu_v15_0_common_get_dpm_freq_by_index(smu, clk_type, + max_dpm_level, max); + if (ret) + goto failed; + } + } + + if (min) { + switch (clk_type) { + case SMU_GFXCLK: + case SMU_SCLK: + *min = clk_table->MinGfxClk; + break; + case SMU_MCLK: + case SMU_UCLK: + min_dpm_level = clk_table->NumMemPstatesEnabled - 1; + break; + case SMU_FCLK: + min_dpm_level = 0; + break; + case SMU_SOCCLK: + min_dpm_level = 0; + break; + default: + ret = -EINVAL; + goto failed; + } + + if (clk_type != SMU_GFXCLK && clk_type != SMU_SCLK) { + ret = smu_v15_0_common_get_dpm_freq_by_index(smu, clk_type, + min_dpm_level, min); + if (ret) + goto failed; + } + } + +failed: + return ret; +} + static int smu_v15_0_common_get_dpm_ultimate_freq(struct smu_context *smu, enum smu_clk_type clk_type, uint32_t *min, uint32_t *max) { - if (clk_type != SMU_VCLK1 && clk_type != SMU_DCLK1) + if (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) == IP_VERSION(15, 0, 5)) + smu_v15_0_5_get_dpm_ultimate_freq(smu, clk_type, min, max); + else if (clk_type != SMU_VCLK1 && clk_type != SMU_DCLK1) smu_v15_0_0_get_dpm_ultimate_freq(smu, clk_type, min, max); return 0; @@ -986,11 +1127,36 @@ static int smu_v15_0_0_get_dpm_level_count(struct smu_context *smu, return 0; } +static int smu_v15_0_5_get_dpm_level_count(struct smu_context *smu, + enum smu_clk_type clk_type, + uint32_t *count) +{ + DpmClocks_t_v15_0_5 *clk_table = smu->smu_table.clocks_table; + + switch (clk_type) { + case SMU_SOCCLK: + *count = clk_table->NumSocClkLevelsEnabled; + break; + case SMU_MCLK: + *count = clk_table->NumMemPstatesEnabled; + break; + case SMU_FCLK: + *count = clk_table->NumFclkLevelsEnabled; + break; + default: + break; + } + + return 0; +} + static int smu_v15_0_common_get_dpm_level_count(struct smu_context *smu, enum smu_clk_type clk_type, uint32_t *count) { - if (clk_type != SMU_VCLK1 && clk_type != SMU_DCLK1) + if (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) == IP_VERSION(15, 0, 5)) + smu_v15_0_5_get_dpm_level_count(smu, clk_type, count); + else if (clk_type != SMU_VCLK1 && clk_type != SMU_DCLK1) smu_v15_0_0_get_dpm_level_count(smu, clk_type, count); return 0; @@ -1363,9 +1529,24 @@ static int smu_v15_0_0_set_fine_grain_gfx_freq_parameters(struct smu_context *sm return 0; } +static int smu_v15_0_5_set_fine_grain_gfx_freq_parameters(struct smu_context *smu) +{ + DpmClocks_t_v15_0_5 *clk_table = smu->smu_table.clocks_table; + + smu->gfx_default_hard_min_freq = clk_table->MinGfxClk; + smu->gfx_default_soft_max_freq = clk_table->MaxGfxClk; + smu->gfx_actual_hard_min_freq = 0; + smu->gfx_actual_soft_max_freq = 0; + + return 0; +} + static int smu_v15_0_common_set_fine_grain_gfx_freq_parameters(struct smu_context *smu) { - smu_v15_0_0_set_fine_grain_gfx_freq_parameters(smu); + if (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) == IP_VERSION(15, 0, 5)) + smu_v15_0_5_set_fine_grain_gfx_freq_parameters(smu); + else + smu_v15_0_0_set_fine_grain_gfx_freq_parameters(smu); return 0; } @@ -1405,9 +1586,36 @@ static int smu_v15_0_0_get_dpm_table(struct smu_context *smu, struct dpm_clocks return 0; } +static int smu_v15_0_5_get_dpm_table(struct smu_context *smu, struct dpm_clocks *clock_table) +{ + DpmClocks_t_v15_0_5 *clk_table = smu->smu_table.clocks_table; + uint8_t idx; + + /* + * Only the Clock information of SOC and + * VPE is copied to provide VPE DPM settings for use. + */ + for (idx = 0; idx < NUM_SOCCLK_DPM_LEVELS; idx++) { + clock_table->SocClocks[idx].Freq = + (idx < clk_table->NumSocClkLevelsEnabled) ? clk_table->SocClocks[idx]:0; + clock_table->SocClocks[idx].Vol = 0; + } + + for (idx = 0; idx < NUM_VPE_DPM_LEVELS; idx++) { + clock_table->VPEClocks[idx].Freq = + (idx < clk_table->VpeClkLevelsEnabled) ? clk_table->VPEClocks[idx]:0; + clock_table->VPEClocks[idx].Vol = 0; + } + + return 0; +} + static int smu_v15_0_common_get_dpm_table(struct smu_context *smu, struct dpm_clocks *clock_table) { - smu_v15_0_0_get_dpm_table(smu, clock_table); + if (amdgpu_ip_version(smu->adev, MP1_HWIP, 0) == IP_VERSION(15, 0, 5)) + smu_v15_0_5_get_dpm_table(smu, clock_table); + else + smu_v15_0_0_get_dpm_table(smu, clock_table); return 0; } -- cgit v1.2.3 From 8c990ee9daa295462df24982ce6878db997a380a Mon Sep 17 00:00:00 2001 From: Pierre-Eric Pelloux-Prayer Date: Mon, 22 Jun 2026 10:11:22 +0200 Subject: drm/amdgpu: move debug_vm handling to amdgpu_cs_parser_fini MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit referenced below restarts the CS if the validation is still in progress. When debug_vm is enabled, all BOs from the CS are invalidated so we will hit an infinite loop. To avoid that, defer BO invalidation to amdgpu_cs_parser_fini. Fixes: 59720bfd8c6d ("drm/amdgpu: restart the CS if some parts of the VM are still invalidated") Signed-off-by: Pierre-Eric Pelloux-Prayer Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index e8eaa3908ca0..6b39c7f0e894 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -1171,19 +1171,6 @@ static int amdgpu_cs_vm_handling(struct amdgpu_cs_parser *p) job->vm_pd_addr = amdgpu_gmc_pd_addr(vm->root.bo); } - if (adev->debug_vm) { - /* Invalidate all BOs to test for userspace bugs */ - amdgpu_bo_list_for_each_entry(e, p->bo_list) { - struct amdgpu_bo *bo = e->bo; - - /* ignore duplicates */ - if (!bo) - continue; - - amdgpu_vm_bo_invalidate(bo, false); - } - } - return 0; } @@ -1371,6 +1358,8 @@ static int amdgpu_cs_submit(struct amdgpu_cs_parser *p, /* Cleanup the parser structure */ static void amdgpu_cs_parser_fini(struct amdgpu_cs_parser *parser) { + struct amdgpu_device *adev = parser->adev; + struct amdgpu_bo_list_entry *e; unsigned int i; amdgpu_sync_free(&parser->sync); @@ -1386,8 +1375,21 @@ static void amdgpu_cs_parser_fini(struct amdgpu_cs_parser *parser) if (parser->ctx) amdgpu_ctx_put(parser->ctx); - if (parser->bo_list) + if (parser->bo_list) { + if (adev->debug_vm) { + /* Invalidate all BOs to test for userspace bugs */ + amdgpu_bo_list_for_each_entry(e, parser->bo_list) { + struct amdgpu_bo *bo = e->bo; + + /* ignore duplicates */ + if (!bo) + continue; + + amdgpu_vm_bo_invalidate(bo, false); + } + } amdgpu_bo_list_put(parser->bo_list); + } for (i = 0; i < parser->nchunks; i++) kvfree(parser->chunks[i].kdata); -- cgit v1.2.3 From 4db4c5ffd5585b72622ecf6ffedf2da258ee23f5 Mon Sep 17 00:00:00 2001 From: Gang Ba Date: Tue, 14 Jul 2026 15:08:57 -0400 Subject: drm/amdkfd: Fix missing authorization check in KFD_IOC_DBG_TRAP_DISABLE Prevent unauthorized termination of active GPU debug sessions. Previously, users with /dev/kfd access could terminate another process's debug session without proper ownership or ptrace authorization. Signed-off-by: Gang Ba Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 2c1a936459ac..6b681d948c04 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -3112,10 +3112,14 @@ static int kfd_ioctl_set_debug_trap(struct file *filep, struct kfd_process *p, v goto out; } - /* Check if target is still PTRACED. */ + /* + * Verify debugger has permission to debug target process. + * For cross-process debugging, require active ptrace relationship. + * This applies to ALL operations to prevent unauthorized interference. + */ rcu_read_lock(); - if (target != p && args->op != KFD_IOC_DBG_TRAP_DISABLE - && ptrace_parent(target->lead_thread) != current) { + if (target != p && ptrace_parent(target->lead_thread) != current + && target->debugger_process != p) { pr_err("PID %i is not PTRACED and cannot be debugged\n", args->pid); r = -EPERM; } -- cgit v1.2.3 From b01e244c82c5d033d7424a64abe4079f3fceb869 Mon Sep 17 00:00:00 2001 From: Shubhankar Milind Sardeshpande Date: Wed, 22 Jul 2026 10:51:56 +0530 Subject: drm/amdgpu: Enable support for PSP 15_0_5 Add PSP 15.0.5 related offsets for GFX to KMD interface and enable support for it. Co-developed-by: Pratik Vishwakarma Signed-off-by: Pratik Vishwakarma Signed-off-by: Shubhankar Milind Sardeshpande Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c | 5 +- drivers/gpu/drm/amd/amdgpu/psp_v15_0.c | 93 ++++++++++++++++++++++----- 3 files changed, 83 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index 44e7d2e3e6df..cf9c4b8df28b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -2529,6 +2529,7 @@ static int amdgpu_discovery_set_psp_ip_blocks(struct amdgpu_device *adev) amdgpu_device_ip_block_add(adev, &psp_v14_0_ip_block); break; case IP_VERSION(15, 0, 0): + case IP_VERSION(15, 0, 5): case IP_VERSION(15, 0, 9): amdgpu_device_ip_block_add(adev, &psp_v15_0_ip_block); break; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c index fdcba94ec0d1..2c23ea625171 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c @@ -275,6 +275,7 @@ static int psp_early_init(struct amdgpu_ip_block *ip_block) psp->boot_time_tmr = false; break; case IP_VERSION(15, 0, 0): + case IP_VERSION(15, 0, 5): case IP_VERSION(15, 0, 9): psp_v15_0_0_set_psp_funcs(psp); psp->boot_time_tmr = false; @@ -3507,8 +3508,10 @@ static int psp_load_non_psp_fw(struct psp_context *psp) amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 0) || amdgpu_ip_version(adev, MP0_HWIP, 0) == - IP_VERSION(15, 0, 8) || + IP_VERSION(15, 0, 5) || amdgpu_ip_version(adev, MP0_HWIP, 0) == + IP_VERSION(15, 0, 8) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 9)) && (ucode->ucode_id == AMDGPU_UCODE_ID_SDMA1 || ucode->ucode_id == AMDGPU_UCODE_ID_SDMA2 || diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c b/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c index 2a4d91368ac6..abaa40b6def9 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v15_0.c @@ -33,9 +33,22 @@ MODULE_FIRMWARE("amdgpu/psp_15_0_0_toc.bin"); MODULE_FIRMWARE("amdgpu/psp_15_0_0_ta.bin"); +MODULE_FIRMWARE("amdgpu/psp_15_0_5_toc.bin"); +MODULE_FIRMWARE("amdgpu/psp_15_0_5_ta.bin"); MODULE_FIRMWARE("amdgpu/psp_15_0_9_toc.bin"); MODULE_FIRMWARE("amdgpu/psp_15_0_9_ta.bin"); +#define regMPASP_PCRU0_MPASP_C2PMSG_64 0x4280 +#define regMPASP_PCRU0_MPASP_C2PMSG_64_BASE_IDX 2 +#define regMPASP_PCRU0_MPASP_C2PMSG_67 0x4283 +#define regMPASP_PCRU0_MPASP_C2PMSG_67_BASE_IDX 2 +#define regMPASP_PCRU0_MPASP_C2PMSG_69 0x4285 +#define regMPASP_PCRU0_MPASP_C2PMSG_69_BASE_IDX 2 +#define regMPASP_PCRU0_MPASP_C2PMSG_70 0x4286 +#define regMPASP_PCRU0_MPASP_C2PMSG_70_BASE_IDX 2 +#define regMPASP_PCRU0_MPASP_C2PMSG_71 0x4287 +#define regMPASP_PCRU0_MPASP_C2PMSG_71_BASE_IDX 2 + static int psp_v15_0_0_init_microcode(struct psp_context *psp) { struct amdgpu_device *adev = psp->adev; @@ -71,14 +84,25 @@ static int psp_v15_0_0_ring_stop(struct psp_context *psp, ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_SMN_C2PMSG_101), 0x80000000, 0x80000000, false); } else { - /* Write the ring destroy command*/ - WREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64, - GFX_CTRL_CMD_ID_DESTROY_RINGS); - /* there might be handshake issue with hardware which needs delay */ - mdelay(20); - /* Wait for response flag (bit 31) */ - ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64), - 0x80000000, 0x80000000, false); + if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 5)) { + /* Write the ring destroy command*/ + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_64, + GFX_CTRL_CMD_ID_DESTROY_RINGS); + /* there might be handshake issue with hardware which needs delay */ + mdelay(20); + /* Wait for response flag (bit 31) */ + ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_64), + 0x80000000, 0x80000000, false); + } else { + /* Write the ring destroy command*/ + WREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64, + GFX_CTRL_CMD_ID_DESTROY_RINGS); + /* there might be handshake issue with hardware which needs delay */ + mdelay(20); + /* Wait for response flag (bit 31) */ + ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64), + 0x80000000, 0x80000000, false); + } } return ret; @@ -118,14 +142,44 @@ static int psp_v15_0_0_ring_create(struct psp_context *psp, 0x80000000, 0x8000FFFF, false); } else { - /* Wait for sOS ready for ring creation */ - ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64), - 0x80000000, 0x80000000, false); + if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 5)) { + /* Wait for sOS ready for ring creation */ + ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_64), + 0x80000000, 0x80000000, false); if (ret) { DRM_ERROR("Failed to wait for trust OS ready for ring creation\n"); return ret; } + /* Write low address of the ring to C2PMSG_69 */ + psp_ring_reg = lower_32_bits(ring->ring_mem_mc_addr); + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_69, psp_ring_reg); + /* Write high address of the ring to C2PMSG_70 */ + psp_ring_reg = upper_32_bits(ring->ring_mem_mc_addr); + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_70, psp_ring_reg); + /* Write size of ring to C2PMSG_71 */ + psp_ring_reg = ring->ring_size; + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_71, psp_ring_reg); + /* Write the ring initialization command to C2PMSG_64 */ + psp_ring_reg = ring_type; + psp_ring_reg = psp_ring_reg << 16; + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_64, psp_ring_reg); + + /* there might be handshake issue with hardware which needs delay */ + mdelay(20); + + /* Wait for response flag (bit 31) in C2PMSG_64 */ + ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_64), + 0x80000000, 0x8000FFFF, false); + } else { + /* Wait for sOS ready for ring creation */ + ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64), + 0x80000000, 0x80000000, false); + if (ret) { + DRM_ERROR("Failed to wait for trust OS ready for ring creation\n"); + return ret; + } + /* Write low address of the ring to C2PMSG_69 */ psp_ring_reg = lower_32_bits(ring->ring_mem_mc_addr); WREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_69, psp_ring_reg); @@ -146,6 +200,7 @@ static int psp_v15_0_0_ring_create(struct psp_context *psp, /* Wait for response flag (bit 31) in C2PMSG_64 */ ret = psp_wait_for(psp, SOC15_REG_OFFSET(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_64), 0x80000000, 0x8000FFFF, false); + } } return ret; @@ -176,8 +231,12 @@ static uint32_t psp_v15_0_0_ring_get_wptr(struct psp_context *psp) if (amdgpu_sriov_vf(adev)) data = RREG32_SOC15(MP0, 0, regMPASP_SMN_C2PMSG_102); - else - data = RREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_67); + else { + if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 5)) + data = RREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_67); + else + data = RREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_67); + } return data; } @@ -190,8 +249,12 @@ static void psp_v15_0_0_ring_set_wptr(struct psp_context *psp, uint32_t value) WREG32_SOC15(MP0, 0, regMPASP_SMN_C2PMSG_102, value); WREG32_SOC15(MP0, 0, regMPASP_SMN_C2PMSG_101, GFX_CTRL_CMD_ID_CONSUME_CMD); - } else - WREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_67, value); + } else { + if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(15, 0, 5)) + WREG32_SOC15(MP0, 0, regMPASP_PCRU0_MPASP_C2PMSG_67, value); + else + WREG32_SOC15(MP0, 0, regMPASP_PCRU1_MPASP_C2PMSG_67, value); + } } static const struct psp_funcs psp_v15_0_0_funcs = { -- cgit v1.2.3 From dcc27ae3092211c913a1bea04618c4faf1234d48 Mon Sep 17 00:00:00 2001 From: Shubhankar Milind Sardeshpande Date: Thu, 16 Jul 2026 18:17:48 +0530 Subject: drm/amdgpu: Fix NBIO 7.11.5 offsets Fix NBIO 7.11.5 related offsets Signed-off-by: Shubhankar Milind Sardeshpande Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c | 146 ++++++++++++++++--------------- 1 file changed, 76 insertions(+), 70 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c b/drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c index c78f0598637f..000516b5845a 100644 --- a/drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c +++ b/drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c @@ -30,70 +30,76 @@ #include "ivsrcid/nbio/irqsrcs_nbif_7_4.h" #include -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL_nbif_4_10 0x4f0aeb -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL1_nbif_4_10 0x4f0aec -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL1_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL_nbif_4_10 0x4f0aed -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL1_nbif_4_10 0x4f0aee -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL1_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL_nbif_4_10 0x4f0aef -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL1_nbif_4_10 0x4f0af0 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL1_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL_nbif_4_10 0x4f0af1 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL1_nbif_4_10 0x4f0af2 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL1_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL_nbif_4_10 0x4f0af3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL1_nbif_4_10 0x4f0af4 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL1_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL_nbif_4_10 0x4f0af5 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL_nbif_4_10_BASE_IDX 3 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL1_nbif_4_10 0x4f0af6 -#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL1_nbif_4_10_BASE_IDX 3 -#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbif_4_10 0x0021 -#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbif_4_10_BASE_IDX 2 - -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_HIGH_nbio_7_11_5 0x8e13 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_HIGH_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_LOW_nbio_7_11_5 0x8e14 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_LOW_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_CNTL_nbio_7_11_5 0x8e15 -#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_CNTL_nbio_7_11_5_BASE_IDX 5 - -#define regBIF_BX0_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5 0x8e4d -#define regBIF_BX0_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX0_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5 0x8e4e -#define regBIF_BX0_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 5 - -#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5 0xd000 -#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5_BASE_IDX 5 - -#define regBIF_BX0_BIF_FB_EN_nbio_7_11_5 0x8e20 -#define regBIF_BX0_BIF_FB_EN_nbio_7_11_5_BASE_IDX 5 - -#define regBIF_BX0_INTERRUPT_CNTL_nbio_7_11_5 0x8e11 -#define regBIF_BX0_INTERRUPT_CNTL_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX0_INTERRUPT_CNTL2_nbio_7_11_5 0x8e12 -#define regBIF_BX0_INTERRUPT_CNTL2_nbio_7_11_5_BASE_IDX 5 - -#define regBIF_BX_PF0_GPU_HDP_FLUSH_REQ_nbio_7_11_5 0x8e26 -#define regBIF_BX_PF0_GPU_HDP_FLUSH_REQ_nbio_7_11_5_BASE_IDX 5 -#define regBIF_BX_PF0_GPU_HDP_FLUSH_DONE_nbio_7_11_5 0x8e27 -#define regBIF_BX_PF0_GPU_HDP_FLUSH_DONE_nbio_7_11_5_BASE_IDX 5 - -#define regBIF_BX_PF0_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5 0x8e17 -#define regBIF_BX_PF0_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 5 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL_nbif_4_10 0x4f0aeb +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL1_nbif_4_10 0x4f0aec +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_0_CTRL1_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL_nbif_4_10 0x4f0aed +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL1_nbif_4_10 0x4f0aee +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_1_CTRL1_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL_nbif_4_10 0x4f0aef +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL1_nbif_4_10 0x4f0af0 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_2_CTRL1_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL_nbif_4_10 0x4f0af1 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL1_nbif_4_10 0x4f0af2 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_3_CTRL1_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL_nbif_4_10 0x4f0af3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL1_nbif_4_10 0x4f0af4 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_4_CTRL1_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL_nbif_4_10 0x4f0af5 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL_nbif_4_10_BASE_IDX 3 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL1_nbif_4_10 0x4f0af6 +#define regGDC_S2A0_S2A_DOORBELL_ENTRY_5_CTRL1_nbif_4_10_BASE_IDX 3 +#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbif_4_10 0x0021 +#define regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbif_4_10_BASE_IDX 2 + +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_HIGH_nbio_7_11_5 0x8e13 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_HIGH_nbio_7_11_5_BASE_IDX 5 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_LOW_nbio_7_11_5 0x8e14 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_BASE_LOW_nbio_7_11_5_BASE_IDX 5 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_CNTL_nbio_7_11_5 0x8e15 +#define regBIF_BX_PF0_DOORBELL_SELFRING_GPA_APER_CNTL_nbio_7_11_5_BASE_IDX 5 + +#define regBIF_BX1_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5 0x012d +#define regBIF_BX1_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 2 +#define regBIF_BX1_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5 0x012e +#define regBIF_BX1_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 2 + +#define regRCC_STRAP1_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5 0x0021 +#define regRCC_STRAP1_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5_BASE_IDX 2 + +#define regBIF_BX1_BIF_FB_EN_nbio_7_11_5 0x0100 +#define regBIF_BX1_BIF_FB_EN_nbio_7_11_5_BASE_IDX 2 + +#define regBIF_BX1_INTERRUPT_CNTL_nbio_7_11_5 0x00f1 +#define regBIF_BX1_INTERRUPT_CNTL_nbio_7_11_5_BASE_IDX 2 +#define regBIF_BX1_INTERRUPT_CNTL2_nbio_7_11_5 0x00f2 +#define regBIF_BX1_INTERRUPT_CNTL2_nbio_7_11_5_BASE_IDX 2 + +#define regBIF_BX_PF1_GPU_HDP_FLUSH_REQ_nbio_7_11_5 0x0106 +#define regBIF_BX_PF1_GPU_HDP_FLUSH_REQ_nbio_7_11_5_BASE_IDX 2 +#define regBIF_BX_PF1_GPU_HDP_FLUSH_DONE_nbio_7_11_5 0x0107 +#define regBIF_BX_PF1_GPU_HDP_FLUSH_DONE_nbio_7_11_5_BASE_IDX 2 + +#define regBIF_BX_PF1_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5 0x00f7 +#define regBIF_BX_PF1_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5_BASE_IDX 2 + +//BIF_BX1_BIF_FB_EN +#define BIF_BX1_BIF_FB_EN__FB_READ_EN__SHIFT_nbio_7_11_5 0x0 +#define BIF_BX1_BIF_FB_EN__FB_WRITE_EN__SHIFT_nbio_7_11_5 0x1 +#define BIF_BX1_BIF_FB_EN__FB_READ_EN_MASK_nbio_7_11_5 0x00000001L +#define BIF_BX1_BIF_FB_EN__FB_WRITE_EN_MASK_nbio_7_11_5 0x00000002L static void nbif_v6_3_1_remap_hdp_registers(struct amdgpu_device *adev) { if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) { - WREG32_SOC15(NBIO, 0, regBIF_BX0_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5, + WREG32_SOC15(NBIO, 0, regBIF_BX1_REMAP_HDP_MEM_FLUSH_CNTL_nbio_7_11_5, adev->rmmio_remap.reg_offset + KFD_MMIO_REMAP_HDP_MEM_FLUSH_CNTL); - WREG32_SOC15(NBIO, 0, regBIF_BX0_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5, + WREG32_SOC15(NBIO, 0, regBIF_BX1_REMAP_HDP_REG_FLUSH_CNTL_nbio_7_11_5, adev->rmmio_remap.reg_offset + KFD_MMIO_REMAP_HDP_REG_FLUSH_CNTL); } else { WREG32_SOC15(NBIO, 0, regBIF_BX0_REMAP_HDP_MEM_FLUSH_CNTL, @@ -110,7 +116,7 @@ static u32 nbif_v6_3_1_get_rev_id(struct amdgpu_device *adev) if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 4)) tmp = RREG32_SOC15(NBIO, 0, regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbif_4_10); else if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) - tmp = RREG32_SOC15(NBIO, 0, regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5); + tmp = RREG32_SOC15(NBIO, 0, regRCC_STRAP1_RCC_DEV0_EPF0_STRAP0_nbio_7_11_5); else tmp = RREG32_SOC15(NBIO, 0, regRCC_STRAP0_RCC_DEV0_EPF0_STRAP0); @@ -124,11 +130,11 @@ static void nbif_v6_3_1_mc_access_enable(struct amdgpu_device *adev, bool enable { if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) { if (enable) - WREG32_SOC15(NBIO, 0, regBIF_BX0_BIF_FB_EN_nbio_7_11_5, - BIF_BX0_BIF_FB_EN__FB_READ_EN_MASK | - BIF_BX0_BIF_FB_EN__FB_WRITE_EN_MASK); + WREG32_SOC15(NBIO, 0, regBIF_BX1_BIF_FB_EN_nbio_7_11_5, + BIF_BX1_BIF_FB_EN__FB_READ_EN_MASK_nbio_7_11_5 | + BIF_BX1_BIF_FB_EN__FB_WRITE_EN_MASK_nbio_7_11_5); else - WREG32_SOC15(NBIO, 0, regBIF_BX0_BIF_FB_EN_nbio_7_11_5, 0); + WREG32_SOC15(NBIO, 0, regBIF_BX1_BIF_FB_EN_nbio_7_11_5, 0); } else { if (enable) WREG32_SOC15(NBIO, 0, regBIF_BX0_BIF_FB_EN, @@ -383,12 +389,12 @@ static void nbif_v6_3_1_ih_control(struct amdgpu_device *adev) /* setup interrupt control */ if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) - WREG32_SOC15(NBIO, 0, regBIF_BX0_INTERRUPT_CNTL2_nbio_7_11_5, + WREG32_SOC15(NBIO, 0, regBIF_BX1_INTERRUPT_CNTL2_nbio_7_11_5, adev->dummy_page_addr >> 8); else WREG32_SOC15(NBIO, 0, regBIF_BX0_INTERRUPT_CNTL2, adev->dummy_page_addr >> 8); - interrupt_cntl = RREG32_SOC15(NBIO, 0, regBIF_BX0_INTERRUPT_CNTL_nbio_7_11_5); + interrupt_cntl = RREG32_SOC15(NBIO, 0, regBIF_BX1_INTERRUPT_CNTL_nbio_7_11_5); /* * BIF_BX0_INTERRUPT_CNTL__IH_DUMMY_RD_OVERRIDE_MASK=0 - dummy read disabled with msi, enabled without msi * BIF_BX0_INTERRUPT_CNTL__IH_DUMMY_RD_OVERRIDE_MASK=1 - dummy read controlled by IH_DUMMY_RD_EN @@ -401,7 +407,7 @@ static void nbif_v6_3_1_ih_control(struct amdgpu_device *adev) IH_REQ_NONSNOOP_EN, 0); if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) - WREG32_SOC15(NBIO, 0, regBIF_BX0_INTERRUPT_CNTL_nbio_7_11_5, interrupt_cntl); + WREG32_SOC15(NBIO, 0, regBIF_BX1_INTERRUPT_CNTL_nbio_7_11_5, interrupt_cntl); else WREG32_SOC15(NBIO, 0, regBIF_BX0_INTERRUPT_CNTL, interrupt_cntl); } @@ -427,7 +433,7 @@ nbif_v6_3_1_get_clockgating_state(struct amdgpu_device *adev, static u32 nbif_v6_3_1_get_hdp_flush_req_offset(struct amdgpu_device *adev) { if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) - return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF0_GPU_HDP_FLUSH_REQ_nbio_7_11_5); + return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF1_GPU_HDP_FLUSH_REQ_nbio_7_11_5); else return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF0_GPU_HDP_FLUSH_REQ); } @@ -435,7 +441,7 @@ static u32 nbif_v6_3_1_get_hdp_flush_req_offset(struct amdgpu_device *adev) static u32 nbif_v6_3_1_get_hdp_flush_done_offset(struct amdgpu_device *adev) { if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) - return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF0_GPU_HDP_FLUSH_DONE_nbio_7_11_5); + return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF1_GPU_HDP_FLUSH_DONE_nbio_7_11_5); else return SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF0_GPU_HDP_FLUSH_DONE); } @@ -622,7 +628,7 @@ static void nbif_v6_3_1_set_reg_remap(struct amdgpu_device *adev) } else { if (amdgpu_ip_version(adev, NBIO_HWIP, 0) == IP_VERSION(7, 11, 5)) adev->rmmio_remap.reg_offset = SOC15_REG_OFFSET(NBIO, 0, - regBIF_BX_PF0_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5) << 2; + regBIF_BX_PF1_HDP_MEM_COHERENCY_FLUSH_CNTL_nbio_7_11_5) << 2; else adev->rmmio_remap.reg_offset = SOC15_REG_OFFSET(NBIO, 0, regBIF_BX_PF0_HDP_MEM_COHERENCY_FLUSH_CNTL) << 2; -- cgit v1.2.3 From ae443117b742c357bfef3a7bddabf76fcf86e9ef Mon Sep 17 00:00:00 2001 From: William Palacek Date: Mon, 20 Jul 2026 12:51:34 -0400 Subject: drm/amdkfd: fix uint32_t overflow in EOP ring buffer size alignment eop_ring_buffer_size in struct queue_properties is a u32. In kfd_queue_acquire_buffers() the expected EOP buffer size is computed as ALIGN(eop_ring_buffer_size, PAGE_SIZE); ALIGN uses typeof(x), so the addition is done in 32-bit. A user-supplied size of 0xFFFFF001 wraps to 0, causing kfd_queue_buffer_get() to skip its exact-size check (gated on size != 0) and accept any BO mapped at the address. On GFX8/GFX9 the MQD cp_hqd_eop_control is then programmed for an 8KB EOP ring backed by a 4KB BO, so CP EOP writes can land past the buffer and fault the GPU. Cast the operand to u64 so the alignment is computed in 64-bit; the size check in kfd_queue_buffer_get() then rejects the oversized request. Fixes: 42ea9cf2f16b ("drm/amdkfd: Relax size checking during queue buffer get") Signed-off-by: William Palacek Reviewed-by: Alysa Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_queue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c index 98a5512b701b..b249e7d1af48 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c @@ -288,7 +288,7 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope } err = kfd_queue_buffer_get(vm, (void *)properties->eop_ring_buffer_address, &properties->eop_buf_bo, - ALIGN(properties->eop_ring_buffer_size, PAGE_SIZE)); + ALIGN((u64)properties->eop_ring_buffer_size, PAGE_SIZE)); if (err) goto out_err_unreserve; } -- cgit v1.2.3 From 314d49abe315cd0d0a872a43f68f08be43a305c8 Mon Sep 17 00:00:00 2001 From: Kanala Ramalingeswara Reddy Date: Thu, 23 Jul 2026 00:10:01 +0530 Subject: drm/amdgpu: enable mode2 reset for SMU IP v15.0.5 Set the default reset method to mode2 for SMU 15.0.5. Signed-off-by: Kanala Ramalingeswara Reddy Reviewed-by: Pratik Vishwakarma Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/soc21.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdgpu/soc21.c b/drivers/gpu/drm/amd/amdgpu/soc21.c index 713746a75d1f..1f9a9c46377a 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc21.c +++ b/drivers/gpu/drm/amd/amdgpu/soc21.c @@ -407,6 +407,7 @@ soc21_asic_reset_method(struct amdgpu_device *adev) case IP_VERSION(14, 0, 4): case IP_VERSION(14, 0, 5): case IP_VERSION(15, 0, 0): + case IP_VERSION(15, 0, 5): case IP_VERSION(15, 0, 9): return AMD_RESET_METHOD_MODE2; default: -- cgit v1.2.3 From 0827279ac10c3a49c981f727e0a61dfb6553e902 Mon Sep 17 00:00:00 2001 From: Tao Zhou Date: Mon, 6 Jul 2026 17:21:39 +0800 Subject: drm/amd/ras: update flip bit setting for uniras The flip bit setting is different if umc number is 8, only NPS1 and NPS2 are supported in this mode. Note: the typical value of umc number is 16, and it can only be 8 or 16 on umc v12. v2: if other umc number is encountered, default setting will be used. Signed-off-by: Tao Zhou Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c | 1 + drivers/gpu/drm/amd/ras/rascore/ras.h | 1 + drivers/gpu/drm/amd/ras/rascore/ras_umc.c | 1 + drivers/gpu/drm/amd/ras/rascore/ras_umc.h | 1 + drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c | 101 +++++++++++++++++------ 5 files changed, 78 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c index 23ffbbc1f68d..c6c2e2cdaf18 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c @@ -284,6 +284,7 @@ static int amdgpu_ras_mgr_init_umc_config(struct amdgpu_device *adev, struct ras_umc_config *umc_cfg = &config->umc_cfg; umc_cfg->umc_vram_type = adev->gmc.vram_type; + umc_cfg->num_umc = adev->gmc.num_umc; return 0; } diff --git a/drivers/gpu/drm/amd/ras/rascore/ras.h b/drivers/gpu/drm/amd/ras/rascore/ras.h index 88b0bac158af..d0018f87172e 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras.h @@ -278,6 +278,7 @@ struct ras_psp_config { struct ras_umc_config { uint32_t umc_vram_type; + uint32_t num_umc; }; struct ras_eeprom_config { diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c index e366fb97293e..dd7a37163110 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c @@ -691,6 +691,7 @@ int ras_umc_hw_init(struct ras_core_context *ras_core) ras_umc->umc_err_data.umc_nps_mode = nps; ras_umc->umc_vram_type = ras_core->config->umc_cfg.umc_vram_type; + ras_umc->num_umc = ras_core->config->umc_cfg.num_umc; if (!ras_umc->umc_vram_type) { RAS_DEV_ERR(ras_core->dev, "Invalid UMC VRAM Type: %u!\n", ras_umc->umc_vram_type); diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h index ee7100f25f51..71b6703193f5 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h @@ -137,6 +137,7 @@ struct ras_umc_err_data { struct ras_umc { u32 umc_ip_version; u32 umc_vram_type; + u32 num_umc; const struct ras_umc_ip_func *ip_func; struct radix_tree_root root; struct mutex tree_lock; diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c b/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c index b809a2f21d73..2c442db1b2f4 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c @@ -64,6 +64,7 @@ static void __get_nps_pa_flip_bits(struct ras_core_context *ras_core, struct umc_flip_bits *flip_bits) { uint32_t vram_type = ras_core->ras_umc.umc_vram_type; + u32 num_umc = ras_core->ras_umc.num_umc; /* default setting */ flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_C2_BIT; @@ -74,41 +75,87 @@ static void __get_nps_pa_flip_bits(struct ras_core_context *ras_core, flip_bits->bit_num = 4; flip_bits->r13_in_pa = UMC_V12_0_PA_R13_BIT; - if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) { + if (num_umc == 16) { + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) { + flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_CH5_BIT; + flip_bits->flip_bits_in_pa[1] = UMC_V12_0_PA_C2_BIT; + flip_bits->flip_bits_in_pa[2] = UMC_V12_0_PA_B1_BIT; + flip_bits->r13_in_pa = UMC_V12_0_PA_R12_BIT; + } else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) { + flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_CH4_BIT; + flip_bits->flip_bits_in_pa[1] = UMC_V12_0_PA_CH5_BIT; + flip_bits->flip_bits_in_pa[2] = UMC_V12_0_PA_B0_BIT; + flip_bits->r13_in_pa = UMC_V12_0_PA_R11_BIT; + } + + switch (vram_type) { + case UMC_VRAM_TYPE_HBM: + /* other nps modes are taken as nps1 */ + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R12_BIT; + else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; + + break; + case UMC_VRAM_TYPE_HBM3E: + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R12_BIT; + flip_bits->flip_row_bit = 12; + + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; + else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R10_BIT; + + break; + default: + RAS_DEV_WARN(ras_core->dev, + "Unknown HBM type, set RAS retire flip bits to the value in NPS1 mode.\n"); + break; + } + } else if (num_umc == 8) { + /* num_umc == 8 base setting */ flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_CH5_BIT; flip_bits->flip_bits_in_pa[1] = UMC_V12_0_PA_C2_BIT; flip_bits->flip_bits_in_pa[2] = UMC_V12_0_PA_B1_BIT; + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; + flip_bits->flip_row_bit = 12; + flip_bits->bit_num = 4; flip_bits->r13_in_pa = UMC_V12_0_PA_R12_BIT; - } else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) { - flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_CH4_BIT; - flip_bits->flip_bits_in_pa[1] = UMC_V12_0_PA_CH5_BIT; - flip_bits->flip_bits_in_pa[2] = UMC_V12_0_PA_B0_BIT; - flip_bits->r13_in_pa = UMC_V12_0_PA_R11_BIT; - } - switch (vram_type) { - case UMC_VRAM_TYPE_HBM: - /* other nps modes are taken as nps1 */ - if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) - flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R12_BIT; - else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) - flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; + /* only NPS1 and NPS2 are supported in this mode */ + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) { + flip_bits->flip_bits_in_pa[0] = UMC_V12_0_PA_CH4_BIT; + flip_bits->flip_bits_in_pa[1] = UMC_V12_0_PA_CH5_BIT; + flip_bits->flip_bits_in_pa[2] = UMC_V12_0_PA_B0_BIT; + flip_bits->r13_in_pa = UMC_V12_0_PA_R11_BIT; + } - break; - case UMC_VRAM_TYPE_HBM3E: - flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R12_BIT; - flip_bits->flip_row_bit = 12; - - if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) - flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; - else if (nps == UMC_MEMORY_PARTITION_MODE_NPS4) - flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R10_BIT; + switch (vram_type) { + case UMC_VRAM_TYPE_HBM: + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R12_BIT; - break; - default: + /* other nps modes are taken as nps1 */ + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R11_BIT; + + break; + case UMC_VRAM_TYPE_HBM3E: + if (nps == UMC_MEMORY_PARTITION_MODE_NPS2) + flip_bits->flip_bits_in_pa[3] = UMC_V12_0_PA_R10_BIT; + + break; + default: + RAS_DEV_WARN(ras_core->dev, + "Unknown HBM type, set RAS retire flip bits to the value in NPS1 mode.\n"); + break; + } + } else { + /* num_umc is always 8 or 16 on umc v12, this path should not be + * entered. + */ RAS_DEV_WARN(ras_core->dev, - "Unknown HBM type, set RAS retire flip bits to the value in NPS1 mode.\n"); - break; + "Unsupported UMC number(%u), use default RAS flip bits setting.\n", + num_umc); } } -- cgit v1.2.3 From cabbba1716c576a0c7089bc665a890cbd2f030b1 Mon Sep 17 00:00:00 2001 From: Victor Zhao Date: Thu, 18 Dec 2025 18:41:35 +0800 Subject: drm/amdgpu: add generic interfaces for PTL requests in virtualization Add Performance Throttle Limiter (PTL) support for SR-IOV guest. Since VF cannot communicate with PSP directly at runtime in SR-IOV environment, use mailbox data fields to pass PTL parameters to PF, and parse response status from host mailbox registers. v2: - remove redundent checks - remove unused marco Signed-off-by: Victor Zhao Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c | 20 +++++++++++++--- drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c | 36 +++++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_virt.h | 11 +++++++++ drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h | 27 ++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/mxgpu_nv.c | 28 ++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/mxgpu_nv.h | 2 ++ 6 files changed, 121 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c index 2c23ea625171..aefe0967f010 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c @@ -1292,8 +1292,19 @@ static int psp_ptl_invoke(struct psp_context *psp, u32 req_code, { struct psp_gfx_cmd_resp *cmd; struct amdgpu_ptl *ptl = &psp->ptl; + struct amdgpu_device *adev = psp->adev; int ret; + if (amdgpu_sriov_vf(adev)) { + ret = amdgpu_virt_ptl_request(adev, req_code, ptl_state, fmt1, fmt2); + if (!ret) { + ptl->enabled = *ptl_state; + ptl->fmt1 = *fmt1; + ptl->fmt2 = *fmt2; + } + return ret; + } + cmd = acquire_psp_cmd_buf(psp); cmd->cmd_id = GFX_CMD_ID_PERF_HW; @@ -1356,15 +1367,18 @@ int amdgpu_ptl_perf_monitor_ctrl(struct amdgpu_device *adev, u32 req_code, if (!adev || !ptl_state || !fmt1 || !fmt2) return -EINVAL; - if (amdgpu_sriov_vf(adev)) - return 0; - psp = &adev->psp; ptl = &psp->ptl; if (ptl->permanently_disabled && *ptl_state == 1) return 0; + if (amdgpu_sriov_vf(adev)) { + ptl_fmt1 = *fmt1; + ptl_fmt2 = *fmt2; + return psp_ptl_invoke(psp, req_code, ptl_state, &ptl_fmt1, &ptl_fmt2); + } + if (amdgpu_ip_version(adev, GC_HWIP, 0) != IP_VERSION(9, 4, 4) || psp->sos.fw_version < 0x0036081a) return -EOPNOTSUPP; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c index 8f4b86063507..b43fc643668d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c @@ -2090,3 +2090,39 @@ int amdgpu_virt_send_remote_ras_cmd(struct amdgpu_device *adev, return ret; } + +int amdgpu_virt_ptl_request(struct amdgpu_device *adev, u32 req_code, + uint32_t *ptl_state, uint32_t *fmt1, uint32_t *fmt2) +{ + int ret; + + if (!ptl_state || !fmt1 || !fmt2) + return -EINVAL; + + if (!amdgpu_sriov_ptl_support(adev) || + !adev->virt.ops || !adev->virt.ops->req_ptl_update) + return -EOPNOTSUPP; + + if (req_code == PSP_PTL_PERF_MON_SET && *ptl_state) { + if (*fmt1 == *fmt2) { + dev_warn(adev->dev, + "PTL formats must be different (fmt1=%u, fmt2=%u)\n", + *fmt1, *fmt2); + return -EINVAL; + } + } + + ret = adev->virt.ops->req_ptl_update(adev, req_code, *ptl_state, *fmt1, *fmt2); + if (ret) { + dev_warn(adev->dev, "VF PTL update request failed: %d\n", ret); + return ret; + } + + if (req_code == PSP_PTL_PERF_MON_QUERY) { + *ptl_state = adev->virt.ptl_state; + *fmt1 = adev->virt.ptl_pref_format1; + *fmt2 = adev->virt.ptl_pref_format2; + } + + return 0; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.h index d8500c3e48a1..bcf7156a4a9e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.h @@ -116,6 +116,8 @@ struct amdgpu_virt_ops { int (*req_ras_chk_criti)(struct amdgpu_device *adev, u64 addr); int (*req_remote_ras_cmd)(struct amdgpu_device *adev, u32 param1, u32 param2, u32 param3); + int (*req_ptl_update)(struct amdgpu_device *adev, + u32 req_code, u32 ptl_state, u32 fmt1, u32 fmt2); }; /* @@ -341,6 +343,11 @@ struct amdgpu_virt { /* Spinlock to protect access to the RLCG register interface */ spinlock_t rlcg_reg_lock; + /* PTL (Performance Throttle Limiter) response from host */ + uint32_t ptl_state; + uint32_t ptl_pref_format1; + uint32_t ptl_pref_format2; + struct mutex access_req_mutex; union amd_sriov_ras_caps ras_en_caps; @@ -447,6 +454,8 @@ static inline bool is_virtual_machine(void) ((adev)->virt.gim_feature & AMDGIM_FEATURE_MES_INFO_ENABLE) #define amdgpu_sriov_is_unitid_support(adev) \ ((adev)->virt.gim_feature & AMDGIM_FEATURE_UNITID_SUPPORT) +#define amdgpu_sriov_ptl_support(adev) \ + ((adev)->virt.gim_feature & AMDGIM_FEATURE_PTL_SUPPORT) #define amdgpu_virt_xgmi_migrate_enabled(adev) \ ((adev)->virt.is_xgmi_node_migrate_enabled && (adev)->gmc.xgmi.node_segment_size != 0) @@ -457,6 +466,8 @@ int amdgpu_virt_request_full_gpu(struct amdgpu_device *adev, bool init); int amdgpu_virt_release_full_gpu(struct amdgpu_device *adev, bool init); int amdgpu_virt_reset_gpu(struct amdgpu_device *adev); void amdgpu_virt_request_init_data(struct amdgpu_device *adev); +int amdgpu_virt_ptl_request(struct amdgpu_device *adev, u32 req_code, + uint32_t *ptl_state, uint32_t *fmt1, uint32_t *fmt2); void amdgpu_virt_ready_to_reset(struct amdgpu_device *adev); int amdgpu_virt_wait_reset(struct amdgpu_device *adev); int amdgpu_virt_alloc_mm_table(struct amdgpu_device *adev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h b/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h index b02561f41b58..33421ccdff04 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgv_sriovmsg.h @@ -404,6 +404,9 @@ enum amd_sriov_mailbox_request_message { MB_REQ_RAS_ERROR_COUNT = 203, MB_REQ_RAS_CPER_DUMP = 204, MB_REQ_RAS_BAD_PAGES = 205, + MB_REQ_RAS_CHK_CRITI = 206, + MB_REQ_RAS_REMOTE_CMD = 207, + MB_REQ_MSG_PTL_UPDATE = 208, }; /* mailbox message send from host to guest */ @@ -424,9 +427,33 @@ enum amd_sriov_mailbox_response_message { MB_RES_MSG_RAS_BAD_PAGES_READY = 15, MB_RES_MSG_RAS_BAD_PAGES_NOTIFICATION = 16, MB_RES_MSG_UNRECOV_ERR_NOTIFICATION = 17, + MB_RES_RAS_CHK_CRITI_READY = 18, + MB_RES_RAS_REMOTE_CMD_READY = 19, + MB_RES_MSG_PTL_UPDATE_READY = 20, MB_RES_MSG_TEXT_MESSAGE = 255 }; +/* + * Generic response status codes for mailbox data fields. + * Used in msg_data[1..N] to indicate operation result. + */ +enum amd_sriov_response_status { + AMD_SRIOV_RESP_SUCCESS = 0, + AMD_SRIOV_RESP_FAIL = 1, + AMD_SRIOV_RESP_UNSUPPORTED = 2, +}; + +/* + * PTL mailbox data format: + * Request: msg_data[1]=req_code, msg_data[2]=ptl_state, msg_data[3]=(fmt1<<16)|fmt2 + * Response: msg_data[1]=(status<<16)|ptl_state, msg_data[2]=(fmt1<<16)|fmt2 + */ +#define AMD_SRIOV_PTL_PACK_FORMATS(fmt1, fmt2) ((((fmt1) & 0xFFFF) << 16) | ((fmt2) & 0xFFFF)) +#define AMD_SRIOV_PTL_UNPACK_STATUS(dw) (((dw) >> 16) & 0xFFFF) +#define AMD_SRIOV_PTL_UNPACK_STATE(dw) ((dw) & 0xFFFF) +#define AMD_SRIOV_PTL_UNPACK_FMT1(dw) (((dw) >> 16) & 0xFFFF) +#define AMD_SRIOV_PTL_UNPACK_FMT2(dw) ((dw) & 0xFFFF) + enum amd_sriov_ras_telemetry_gpu_block { RAS_TELEMETRY_GPU_BLOCK_UMC = 0, RAS_TELEMETRY_GPU_BLOCK_SDMA = 1, diff --git a/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.c b/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.c index f2e456390b27..eb537b2ae685 100644 --- a/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.c +++ b/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.c @@ -214,6 +214,9 @@ send_request: case IDH_REQ_RAS_REMOTE_CMD: event = IDH_REQ_RAS_REMOTE_CMD_READY; break; + case IDH_REQ_PTL_UPDATE: + event = IDH_PTL_UPDATE_READY; + break; default: break; } @@ -253,6 +256,23 @@ send_request: adev->virt.fw_reserve.checksum_key = RREG32_NO_KIQ(mmMAILBOX_MSGBUF_RCV_DW2); } + + /* Retrieve PTL response from mailbox */ + if (req == IDH_REQ_PTL_UPDATE) { + u32 dw1 = RREG32_NO_KIQ(mmMAILBOX_MSGBUF_RCV_DW1); + u32 dw2 = RREG32_NO_KIQ(mmMAILBOX_MSGBUF_RCV_DW2); + u32 status = AMD_SRIOV_PTL_UNPACK_STATUS(dw1); + + if (status == AMD_SRIOV_RESP_UNSUPPORTED) { + r = -EOPNOTSUPP; + } else if (status == AMD_SRIOV_RESP_FAIL) { + r = -EIO; + } else { + adev->virt.ptl_state = AMD_SRIOV_PTL_UNPACK_STATE(dw1); + adev->virt.ptl_pref_format1 = AMD_SRIOV_PTL_UNPACK_FMT1(dw2); + adev->virt.ptl_pref_format2 = AMD_SRIOV_PTL_UNPACK_FMT2(dw2); + } + } } out: @@ -597,6 +617,13 @@ static int xgpu_nv_req_remote_ras_cmd(struct amdgpu_device *adev, adev, IDH_REQ_RAS_REMOTE_CMD, param1, param2, param3); } +static int xgpu_nv_req_ptl_update(struct amdgpu_device *adev, + u32 req_code, u32 ptl_state, u32 fmt1, u32 fmt2) +{ + return xgpu_nv_send_access_requests_with_param(adev, IDH_REQ_PTL_UPDATE, + req_code, ptl_state, AMD_SRIOV_PTL_PACK_FORMATS(fmt1, fmt2)); +} + const struct amdgpu_virt_ops xgpu_nv_virt_ops = { .req_full_gpu = xgpu_nv_request_full_gpu_access, .rel_full_gpu = xgpu_nv_release_full_gpu_access, @@ -612,4 +639,5 @@ const struct amdgpu_virt_ops xgpu_nv_virt_ops = { .req_bad_pages = xgpu_nv_req_ras_bad_pages, .req_ras_chk_criti = xgpu_nv_check_vf_critical_region, .req_remote_ras_cmd = xgpu_nv_req_remote_ras_cmd, + .req_ptl_update = xgpu_nv_req_ptl_update, }; diff --git a/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.h b/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.h index dc57a4f697ee..5023113e7600 100644 --- a/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.h +++ b/drivers/gpu/drm/amd/amdgpu/mxgpu_nv.h @@ -45,6 +45,7 @@ enum idh_request { IDH_REQ_RAS_BAD_PAGES = 205, IDH_REQ_RAS_CHK_CRITI = 206, IDH_REQ_RAS_REMOTE_CMD = 207, + IDH_REQ_PTL_UPDATE = 208 }; enum idh_event { @@ -66,6 +67,7 @@ enum idh_event { IDH_UNRECOV_ERR_NOTIFICATION = 17, IDH_REQ_RAS_CHK_CRITI_READY = 18, IDH_REQ_RAS_REMOTE_CMD_READY = 19, + IDH_PTL_UPDATE_READY = 20, IDH_TEXT_MESSAGE = 255, }; -- cgit v1.2.3 From 1c760249a5011b91ffb42fc23926a956a1936d71 Mon Sep 17 00:00:00 2001 From: Victor Zhao Date: Thu, 18 Dec 2025 18:41:52 +0800 Subject: drm/amdgpu: always get PTL state from PSP under sriov In SR-IOV mode, always query the latest PTL status from host via psp_performance_monitor_hw() to ensure the sysfs shows real-time hardware state. Signed-off-by: Victor Zhao Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c index aefe0967f010..e03b94dc111d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c @@ -1517,6 +1517,18 @@ static ssize_t ptl_enable_show(struct device *dev, struct device_attribute *attr struct drm_device *ddev = dev_get_drvdata(dev); struct amdgpu_device *adev = drm_to_adev(ddev); struct amdgpu_ptl *ptl = &adev->psp.ptl; + uint32_t ptl_state, fmt1, fmt2; + int ret; + + if (amdgpu_sriov_vf(adev)) { + ptl_state = ptl->enabled; + fmt1 = ptl->fmt1; + fmt2 = ptl->fmt2; + ret = amdgpu_ptl_perf_monitor_ctrl(adev, PSP_PTL_PERF_MON_QUERY, + &ptl_state, &fmt1, &fmt2); + if (ret) + return ret; + } if (ptl->permanently_disabled) return sysfs_emit(buf, "permanently disabled\n"); @@ -1575,11 +1587,23 @@ static ssize_t ptl_format_show(struct device *dev, struct device_attribute *attr { struct drm_device *ddev = dev_get_drvdata(dev); struct amdgpu_device *adev = drm_to_adev(ddev); - struct psp_context *psp = &adev->psp; + struct amdgpu_ptl *ptl = &adev->psp.ptl; + uint32_t ptl_state, fmt1, fmt2; + int ret; + + if (amdgpu_sriov_vf(adev)) { + ptl_state = ptl->enabled; + fmt1 = ptl->fmt1; + fmt2 = ptl->fmt2; + ret = amdgpu_ptl_perf_monitor_ctrl(adev, PSP_PTL_PERF_MON_QUERY, + &ptl_state, &fmt1, &fmt2); + if (ret) + return ret; + } return sysfs_emit(buf, "%s,%s\n", - amdgpu_ptl_fmt_str[psp->ptl.fmt1], - amdgpu_ptl_fmt_str[psp->ptl.fmt2]); + amdgpu_ptl_fmt_str[ptl->fmt1], + amdgpu_ptl_fmt_str[ptl->fmt2]); } static umode_t amdgpu_ptl_is_visible(struct kobject *kobj, struct attribute *attr, int idx) -- cgit v1.2.3 From 6085f8289ef1f8ee1409992aa4e8fa7331192e58 Mon Sep 17 00:00:00 2001 From: Victor Zhao Date: Thu, 18 Dec 2025 18:42:04 +0800 Subject: drm/amdgpu: convert ptl_hw_supported to enum Convert ptl_hw_supported to enum with three states: - AMDGPU_PTL_HW_UNINIT: not yet initialized - AMDGPU_PTL_HW_SUPPORTED: initialized and supported - AMDGPU_PTL_HW_NOT_SUPPORTED: initialized and not supported This allows skipping PTL initialization attempts when hardware is known to not support it, avoiding repeated initialization failures after GPU resets. v2:move ptl_hw_supported_state to AMDGPU_PTL_HW_NOT_SUPPORTED regardless of error code during first time initialization. Print init fail log when error code is not EOPNOTSUPP. Signed-off-by: Victor Zhao Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c | 6 +++--- drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 19 +++++++++++++++---- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 11 +++++++---- drivers/gpu/drm/amd/include/amdgpu_ptl.h | 9 ++++++++- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c index e03b94dc111d..04f6ebf31cca 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c @@ -1613,7 +1613,7 @@ static umode_t amdgpu_ptl_is_visible(struct kobject *kobj, struct attribute *att struct amdgpu_device *adev = drm_to_adev(ddev); /* Only show PTL sysfs files if PTL hardware is supported */ - if (!adev->psp.ptl.hw_supported) + if (adev->psp.ptl.hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) return 0; return attr->mode; @@ -1624,7 +1624,7 @@ int amdgpu_ptl_sysfs_init(struct amdgpu_device *adev) struct amdgpu_ptl *ptl = &adev->psp.ptl; int ret; - if (!ptl->hw_supported) + if (ptl->hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) return 0; if (ptl->ptl_sysfs_created) @@ -1641,7 +1641,7 @@ void amdgpu_ptl_sysfs_fini(struct amdgpu_device *adev) { struct amdgpu_ptl *ptl = &adev->psp.ptl; - if (!ptl->hw_supported) + if (ptl->hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) return; if (!ptl->ptl_sysfs_created) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c index 9512fef81d84..b086fabb8e1e 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c @@ -2400,7 +2400,10 @@ static int gfx_v9_4_3_perf_monitor_ptl_init(struct amdgpu_device *adev, bool ena if (!adev->psp.funcs) return -EOPNOTSUPP; - if (!ptl->hw_supported) { + if (ptl->hw_supported_state == AMDGPU_PTL_HW_NOT_SUPPORTED) + return -EOPNOTSUPP; + + if (ptl->hw_supported_state == AMDGPU_PTL_HW_UNINIT) { fmt1 = GFX_FTYPE_VECTOR; fmt2 = GFX_FTYPE_F8; } else { @@ -2411,10 +2414,17 @@ static int gfx_v9_4_3_perf_monitor_ptl_init(struct amdgpu_device *adev, bool ena /* initialize PTL with default formats: GFX_FTYPE_VECTOR & GFX_FTYPE_F8 */ r = amdgpu_ptl_perf_monitor_ctrl(adev, PSP_PTL_PERF_MON_SET, &ptl_state, &fmt1, &fmt2); - if (r) + if (r) { + if (ptl->hw_supported_state == AMDGPU_PTL_HW_UNINIT) + ptl->hw_supported_state = AMDGPU_PTL_HW_NOT_SUPPORTED; + + if (r != -EOPNOTSUPP) + dev_err(adev->dev, "PTL initialization failed (%d)\n", r); + return r; + } - ptl->hw_supported = true; + ptl->hw_supported_state = AMDGPU_PTL_HW_SUPPORTED; atomic_set(&ptl->disable_ref, 0); if (!enable && !amdgpu_in_reset(adev) && !adev->in_suspend) { @@ -2459,7 +2469,8 @@ static int gfx_v9_4_3_hw_fini(struct amdgpu_ip_block *ip_block) struct amdgpu_device *adev = ip_block->adev; int i, num_xcc; - if (adev->psp.ptl.hw_supported && !amdgpu_in_reset(adev)) + if (adev->psp.ptl.hw_supported_state == AMDGPU_PTL_HW_SUPPORTED && + !amdgpu_in_reset(adev)) gfx_v9_4_3_perf_monitor_ptl_init(adev, false); amdgpu_irq_put(adev, &adev->gfx.bad_op_irq, 0); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 6b681d948c04..dd7a7a18466d 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1783,6 +1783,9 @@ static int kfd_ptl_control(struct kfd_process_device *pdd, bool enable) uint32_t ptl_state = enable ? 1 : 0; int ret; + if (ptl->hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) + return -EOPNOTSUPP; + if (!pdd->dev->kfd2kgd || !pdd->dev->kfd2kgd->ptl_ctrl) return -EOPNOTSUPP; @@ -1801,7 +1804,7 @@ int kfd_ptl_disable_request(struct kfd_process_device *pdd, struct amdgpu_ptl *ptl = &adev->psp.ptl; int ret = 0; - if (!ptl->hw_supported) + if (ptl->hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) return -EOPNOTSUPP; mutex_lock(&ptl->mutex); @@ -1833,7 +1836,7 @@ int kfd_ptl_disable_release(struct kfd_process_device *pdd, struct amdgpu_ptl *ptl = &adev->psp.ptl; int ret = 0; - if (!ptl->hw_supported) + if (ptl->hw_supported_state != AMDGPU_PTL_HW_SUPPORTED) return -EOPNOTSUPP; mutex_lock(&ptl->mutex); @@ -3353,7 +3356,7 @@ static inline uint32_t profile_lock_device(struct kfd_process *p, kfd->profiler_process = p; status = 0; mutex_unlock(&kfd->profiler_lock); - if (ptl->hw_supported) { + if (ptl->hw_supported_state == AMDGPU_PTL_HW_SUPPORTED) { status = kfd_ptl_disable_request(pdd, p); if (status != 0) dev_err(kfd_device, @@ -3371,7 +3374,7 @@ static inline uint32_t profile_lock_device(struct kfd_process *p, status = 0; mutex_unlock(&kfd->profiler_lock); - if (ptl->hw_supported) { + if (ptl->hw_supported_state == AMDGPU_PTL_HW_SUPPORTED) { status = kfd_ptl_disable_release(pdd, p); if (status) dev_err(kfd_device, diff --git a/drivers/gpu/drm/amd/include/amdgpu_ptl.h b/drivers/gpu/drm/amd/include/amdgpu_ptl.h index 154b8da3bfa9..eead62b4b327 100644 --- a/drivers/gpu/drm/amd/include/amdgpu_ptl.h +++ b/drivers/gpu/drm/amd/include/amdgpu_ptl.h @@ -39,11 +39,18 @@ enum amdgpu_ptl_disable_source { AMDGPU_PTL_DISABLE_PROFILER, AMDGPU_PTL_DISABLE_MAX, }; + +enum amdgpu_ptl_hw_supported_state { + AMDGPU_PTL_HW_UNINIT = 0, /* Not yet initialized */ + AMDGPU_PTL_HW_SUPPORTED, /* Initialized and supported */ + AMDGPU_PTL_HW_NOT_SUPPORTED, /* Initialized and not supported */ +}; + struct amdgpu_ptl { enum amdgpu_ptl_fmt fmt1; enum amdgpu_ptl_fmt fmt2; bool enabled; - bool hw_supported; + enum amdgpu_ptl_hw_supported_state hw_supported_state; bool permanently_disabled; /* PTL disable reference counting */ atomic_t disable_ref; -- cgit v1.2.3 From 47a71f452c857596e896c5b4b1cde01c8e2c38f5 Mon Sep 17 00:00:00 2001 From: Tao Zhou Date: Wed, 8 Jul 2026 18:19:09 +0800 Subject: drm/amd/ras: detect old ras eeprom format Handler of some formats will be implemented in the future. UMC_CHANNEL_IDX_V2 is a flag to indicate v2 format channel index stored in eeprom, the flag was retired in v3 and save_nps is introduced in v3, so they have no conflict. eeprom format v1: store channel index within a umc instance in eeprom range in UMC v12: 0 ~ 7 eeprom format v2: store global channel index in eeprom range in UMC v12: 0 ~ 127 v2: change the bit range of save_nps from [40:47] to [40:46], UMC_CHANNEL_IDX_V2 use bit 47. use RAS_DEV_WARN_RATELIMITED for retire record check, avoid log noise. Signed-off-by: Tao Zhou Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/rascore/ras_eeprom.h | 4 ++-- drivers/gpu/drm/amd/ras/rascore/ras_umc.c | 2 +- drivers/gpu/drm/amd/ras/rascore/ras_umc.h | 20 ++++++++++++++++++ drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c | 28 ++++++++++++++++++------- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.h b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.h index f2c001ef64e1..5c3e92f710b4 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras_eeprom.h @@ -36,10 +36,10 @@ /* * Bad address pfn : eeprom_umc_record.retired_row_pfn[39:0], - * nps mode: eeprom_umc_record.retired_row_pfn[47:40] + * nps mode: eeprom_umc_record.retired_row_pfn[46:40] */ #define EEPROM_RECORD_UMC_ADDR_MASK 0xFFFFFFFFFFULL -#define EEPROM_RECORD_UMC_NPS_MASK 0xFF0000000000ULL +#define EEPROM_RECORD_UMC_NPS_MASK 0x7F0000000000ULL #define EEPROM_RECORD_UMC_NPS_SHIFT 40 #define EEPROM_RECORD_UMC_NPS_MODE(RECORD) \ diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c index dd7a37163110..f59f0b6cad06 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.c @@ -346,7 +346,7 @@ static bool ras_umc_check_retired_record(struct ras_core_context *ras_core, if (ras_umc->ip_func && ras_umc->ip_func->eeprom_record_to_nps_record) { ret = ras_umc->ip_func->eeprom_record_to_nps_record(ras_core, record, nps); if (ret) - RAS_DEV_WARN(ras_core->dev, + RAS_DEV_WARN_RATELIMITED(ras_core->dev, "Failed to adjust eeprom record, ret:%d", ret); } return false; diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h index 71b6703193f5..3d902b0d663a 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc.h @@ -46,6 +46,26 @@ #define UMC_ECC_NEW_DETECTED_TAG 0x1 #define UMC_INV_MEM_PFN (0xFFFFFFFFFFFFFFFF) +/* + * a flag to indicate v2 format channel index stored in eeprom + * + * v1: store channel index within a umc instance in eeprom + * range in UMC v12: 0 ~ 7 + * v2: store global channel index in eeprom + * range in UMC v12: 0 ~ 127 + * + * NOTE: it's better to store it in eeprom_table_record.mem_channel, + * but there is only 8 bits in mem_channel, and the channel number may + * increase in the future, we decide to save it in + * eeprom_table_record.retired_page. retired_page is useless in v2, + * we depend on eeprom_table_record.address instead of retired_page in v2. + * Only 48 bits are saved on eeprom, use bit 47 here. + * + * UMC_CHANNEL_IDX_V2 is replaced by nps value in cur_nps_retired_row_pfn + * in eeprom v3 format, so they have no conflict. + */ +#define UMC_CHANNEL_IDX_V2 BIT_ULL(47) + /* three column bits and one row bit in MCA address flip * in bad page retirement */ diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c b/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c index 2c442db1b2f4..6bf30dee8d17 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.c @@ -460,16 +460,30 @@ static int convert_eeprom_record_to_nps_addr(struct ras_core_context *ras_core, static int umc_v12_0_eeprom_record_to_nps_record(struct ras_core_context *ras_core, struct eeprom_umc_record *record, uint32_t nps) { - uint64_t pa = 0; + uint64_t ch_idx_v2, pa = 0; + uint32_t save_nps; int ret = 0; - if (nps == EEPROM_RECORD_UMC_NPS_MODE(record) && !ras_fw_eeprom_supported(ras_core)) { - record->cur_nps_retired_row_pfn = EEPROM_RECORD_UMC_ADDR_PFN(record); - } else { - ret = convert_eeprom_record_to_nps_addr(ras_core, + save_nps = EEPROM_RECORD_UMC_NPS_MODE(record); + /* eeprom v2 has no stored nps, always convert if the flag is set */ + ch_idx_v2 = record->retired_row_pfn & UMC_CHANNEL_IDX_V2; + + if (save_nps || ch_idx_v2) { + if ((nps == save_nps) && !ras_fw_eeprom_supported(ras_core)) { + record->cur_nps_retired_row_pfn = + EEPROM_RECORD_UMC_ADDR_PFN(record); + } else { + ret = convert_eeprom_record_to_nps_addr(ras_core, record, &pa, nps); - if (!ret) - record->cur_nps_retired_row_pfn = RAS_ADDR_TO_PFN(pa); + if (!ret) + record->cur_nps_retired_row_pfn = RAS_ADDR_TO_PFN(pa); + } + } else { + /* old eeprom data format, the scope of channel index is + * limited to umc instance + */ + /* TODO */ + ret = -EOPNOTSUPP; } record->cur_nps = nps; -- cgit v1.2.3 From f6eed7acfd30099ef7baeb6ba45bb59daad80631 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Fri, 24 Jul 2026 07:41:29 +0800 Subject: drm/amd/pm: fix pptable use-after-free amdgpu_dpm_get_pp_table() returns a pointer to a driver-owned power table after dropping adev->pm.mutex. The sysfs path then copies from that pointer. A concurrent pp_table write can replace and free the allocation during the copy, causing a use-after-free. Change the DPM interface to copy into caller-provided storage while the mutex is held. Keep the size-only query for attribute discovery without exposing the driver-owned pointer. Fixes: 1684d3ba4885 ("drm/amd/amdgpu: change pptable output format from ASCII to binary") Signed-off-by: Yang Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/amdgpu_dpm.c | 14 +++++++++++--- drivers/gpu/drm/amd/pm/amdgpu_pm.c | 13 +++---------- drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h | 3 ++- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c index c09c60ad3f38..df863a451f26 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c @@ -1183,12 +1183,14 @@ int amdgpu_dpm_dispatch_task(struct amdgpu_device *adev, return ret; } -int amdgpu_dpm_get_pp_table(struct amdgpu_device *adev, char **table) +int amdgpu_dpm_get_pp_table(struct amdgpu_device *adev, char *table, + size_t size) { const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; + char *pptable = NULL; int ret = 0; - if (!table) + if ((!table && size) || (table && !size)) return -EINVAL; if (amdgpu_sriov_vf(adev) || !pp_funcs->get_pp_table || adev->scpm_enabled) @@ -1196,7 +1198,13 @@ int amdgpu_dpm_get_pp_table(struct amdgpu_device *adev, char **table) mutex_lock(&adev->pm.mutex); ret = pp_funcs->get_pp_table(adev->powerplay.pp_handle, - table); + &pptable); + if (ret > 0 && !pptable) { + ret = -EINVAL; + } else if (ret > 0 && table) { + ret = min_t(size_t, ret, size); + memcpy(table, pptable, ret); + } mutex_unlock(&adev->pm.mutex); return ret; diff --git a/drivers/gpu/drm/amd/pm/amdgpu_pm.c b/drivers/gpu/drm/amd/pm/amdgpu_pm.c index f5a5d72b4108..d473976ddae5 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_pm.c @@ -595,25 +595,19 @@ static ssize_t amdgpu_get_pp_table(struct device *dev, { struct drm_device *ddev = dev_get_drvdata(dev); struct amdgpu_device *adev = drm_to_adev(ddev); - char *table = NULL; int size, ret; ret = amdgpu_pm_get_access_if_active(adev); if (ret) return ret; - size = amdgpu_dpm_get_pp_table(adev, &table); + size = amdgpu_dpm_get_pp_table(adev, buf, PAGE_SIZE - 1); amdgpu_pm_put_access(adev); if (size <= 0) return size; - if (size >= PAGE_SIZE) - size = PAGE_SIZE - 1; - - memcpy(buf, table, size); - return size; } @@ -2733,10 +2727,9 @@ static int default_attr_update(struct amdgpu_device *adev, struct amdgpu_device_ *states = ATTR_STATE_UNSUPPORTED; } else if (DEVICE_ATTR_IS(pp_table)) { int ret; - char *tmp = NULL; - ret = amdgpu_dpm_get_pp_table(adev, &tmp); - if (ret == -EOPNOTSUPP || !tmp) + ret = amdgpu_dpm_get_pp_table(adev, NULL, 0); + if (ret <= 0) *states = ATTR_STATE_UNSUPPORTED; else *states = ATTR_STATE_SUPPORTED; diff --git a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h index c7ea29385682..a1d1c9f5b1d4 100644 --- a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h +++ b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h @@ -487,7 +487,8 @@ int amdgpu_dpm_get_pp_num_states(struct amdgpu_device *adev, int amdgpu_dpm_dispatch_task(struct amdgpu_device *adev, enum amd_pp_task task_id, enum amd_pm_state_type *user_state); -int amdgpu_dpm_get_pp_table(struct amdgpu_device *adev, char **table); +int amdgpu_dpm_get_pp_table(struct amdgpu_device *adev, char *table, + size_t size); int amdgpu_dpm_set_fine_grain_clk_vol(struct amdgpu_device *adev, uint32_t type, long *input, -- cgit v1.2.3 From 862333bb48693ecafcae25af0c9d9ec31015ac77 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Thu, 23 Jul 2026 23:18:33 +0800 Subject: drm/amd/pm: fix torn gpu metrics reads amdgpu_dpm_get_gpu_metrics() returns a pointer to the shared metrics cache after dropping adev->pm.mutex. The sysfs path then copies from that pointer. Another reader can refresh the cache in place during the copy and return a snapshot containing data from two generations. Pass caller-provided storage through the DPM interface and copy the metrics while the mutex is held. This keeps the cache pointer private and makes each sysfs read observe one complete sample. Fixes: 25c933b1c4fc ("drm/amd/powerplay: add new sysfs interface for retrieving gpu metrics(V2)") Signed-off-by: Yang Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/pm/amdgpu_dpm.c | 12 +++++++++--- drivers/gpu/drm/amd/pm/amdgpu_pm.c | 8 +------- drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c index df863a451f26..8f59ea12fe0e 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c @@ -1434,17 +1434,23 @@ int amdgpu_dpm_set_power_profile_mode(struct amdgpu_device *adev, return ret; } -int amdgpu_dpm_get_gpu_metrics(struct amdgpu_device *adev, void **table) +ssize_t amdgpu_dpm_get_gpu_metrics(struct amdgpu_device *adev, void *buf, + size_t size) { const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; - int ret = 0; + void *table; + ssize_t ret; if (!pp_funcs->get_gpu_metrics) return 0; mutex_lock(&adev->pm.mutex); ret = pp_funcs->get_gpu_metrics(adev->powerplay.pp_handle, - table); + &table); + if (ret > 0) { + ret = min_t(ssize_t, ret, size); + memcpy(buf, table, ret); + } mutex_unlock(&adev->pm.mutex); return ret; diff --git a/drivers/gpu/drm/amd/pm/amdgpu_pm.c b/drivers/gpu/drm/amd/pm/amdgpu_pm.c index d473976ddae5..e4fc33e3c951 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_pm.c @@ -1781,7 +1781,6 @@ static ssize_t amdgpu_get_gpu_metrics(struct device *dev, { struct drm_device *ddev = dev_get_drvdata(dev); struct amdgpu_device *adev = drm_to_adev(ddev); - void *gpu_metrics; ssize_t size = 0; int ret; @@ -1789,15 +1788,10 @@ static ssize_t amdgpu_get_gpu_metrics(struct device *dev, if (ret) return ret; - size = amdgpu_dpm_get_gpu_metrics(adev, &gpu_metrics); + size = amdgpu_dpm_get_gpu_metrics(adev, buf, PAGE_SIZE - 1); if (size <= 0) goto out; - if (size >= PAGE_SIZE) - size = PAGE_SIZE - 1; - - memcpy(buf, gpu_metrics, size); - out: amdgpu_pm_put_access(adev); diff --git a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h index a1d1c9f5b1d4..8e8a052e1d53 100644 --- a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h +++ b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h @@ -518,7 +518,8 @@ int amdgpu_dpm_get_power_profile_mode(struct amdgpu_device *adev, char *buf); int amdgpu_dpm_set_power_profile_mode(struct amdgpu_device *adev, long *input, uint32_t size); -int amdgpu_dpm_get_gpu_metrics(struct amdgpu_device *adev, void **table); +ssize_t amdgpu_dpm_get_gpu_metrics(struct amdgpu_device *adev, void *buf, + size_t size); ssize_t amdgpu_dpm_get_xcp_metrics(struct amdgpu_device *adev, int xcp_id, void *table); ssize_t amdgpu_dpm_get_temp_metrics(struct amdgpu_device *adev, -- cgit v1.2.3 From 7e1b4bdb0e05a7f65fd2a5ee43bae39c9ecefbef Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 23 Jul 2026 14:01:59 -0400 Subject: drm/amd/display: Fix flip-done timeouts on mode1 reset The vblank on/off callbacks mixed use of amdgpu_irq_get/put() and amdgpu_dm_crtc_set_vupdate_irq() to enable and disable IRQs. With get/put, base driver will callback into DC to disable IRQs when refcount == 0. With set_vupdate_irq(), DC is called directly to disable IRQs, bypassing base driver's refcount tracking. During gpu reset, base driver can restore IRQs via amdgpu_irq_gpu_reset_resume_helper() > amdgpu_irq_update(). So if get/put() is not used (i.e. refcount == 0), then vupdate_irq will be disabled. This is problematic if DRM requests vblank on before amdgpu_irq_update() is called: drm_vblank_on() > set_vupdate_irq() enables vupdate_irq, but the refcount is still 0. gpu_reset_resume_helper() > irq_update() then immediately disables it, thus leading to flip done timeouts. This is made worse on DCN since VUPDATE_NO_LOCK is the only IRQ enabled. Prior to the "Fixes:" change, a combination of GRPH_FLIP and VSTARTUP IRQs were used, and they used get/put(). This explains why the change exposed this issue. Fix by using get/put() instead of set_vupdate_irq(). DCE is unchanged, since it relies on unbalanced enable/disable calls based on VRR status, and hence requires direct set_vupdate_irq(). Plus, it also uses GRPH_FLIP and VLINE IRQs, which are properly tracked by get/put(). Fixes: c87e6635d2db ("drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock") Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Leo Li Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 05d6915f9a6b..079d4ccc88da 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -287,10 +287,19 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable) * is enabled. On DCE, vupdate is only needed in VRR mode. */ if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) { - rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, enable); + if (enable) { + rc = amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Get vupdate_irq ret=%d\n", rc); + } else { + rc = amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Put vupdate_irq ret=%d\n", rc); + } } else if (dc_supports_vrr(dm->dc->ctx->dce_version)) { if (enable) { - /* vblank irq on -> Only need vupdate irq in vrr mode */ + /* vblank irq on -> Only need vupdate irq in vrr mode + * Not ref-counted since we need explicit enable/disable + * for DCE VRR handling + */ if (amdgpu_dm_crtc_vrr_active(acrtc_state)) rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, true); } else { -- cgit v1.2.3 From 2c381b75fae81fc34b27fe9965466e904b7e4663 Mon Sep 17 00:00:00 2001 From: Philip Yang Date: Sun, 21 Jun 2026 11:46:37 -0400 Subject: drm/amdkfd: Avoid topology_lock in kfd_mmap Use process-local GPU lookup in kfd_mmap() instead of kfd_device_by_id(), so mmap path does not take topology_lock while holding mmap_lock. Lockdep warning: possible circular locking dependency detected. Chain: &topology_lock -> &root->kernfs_rwsem -> &mm->mmap_lock Backtrace #0 (&topology_lock): down_read+0x8b/0x490 kfd_device_by_id+0x1d/0xf0 [amdgpu] kfd_mmap+0x11e/0x750 [amdgpu] __mmap_region+0x101f/0x25e0 mmap_region+0x228/0x2e0 do_mmap+0x9f2/0x1070 vm_mmap_pgoff+0x25d/0x430 Backtrace #1 (&root->kernfs_rwsem): down_write+0x80/0x200 kernfs_add_one+0x30/0x6a0 kernfs_create_dir_ns+0xd2/0x160 sysfs_create_dir_ns+0x12c/0x2a0 kobject_add_internal+0x280/0x8e0 kfd_topology_update_sysfs+0xb85/0x1970 [amdgpu] Backtrace #2 (&mm->mmap_lock): down_read_killable+0x8e/0x510 lock_mm_and_find_vma+0x299/0xb00 do_user_addr_fault+0x3fc/0xf80 exc_page_fault+0x73/0x110 filldir64+0x1aa/0x610 kernfs_fop_readdir+0x3c2/0x810 Signed-off-by: Philip Yang Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index dd7a7a18466d..309510e23315 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -3722,10 +3722,10 @@ static int kfd_mmio_mmap(struct kfd_node *dev, struct kfd_process *process, vma->vm_page_prot); } - static int kfd_mmap(struct file *filep, struct vm_area_struct *vma) { struct kfd_process *process; + struct kfd_process_device *pdd; struct kfd_node *dev = NULL; unsigned long mmap_offset; unsigned int gpu_id; @@ -3739,8 +3739,10 @@ static int kfd_mmap(struct file *filep, struct vm_area_struct *vma) mmap_offset = vma->vm_pgoff << PAGE_SHIFT; gpu_id = KFD_MMAP_GET_GPU_ID(mmap_offset); - if (gpu_id) - dev = kfd_device_by_id(gpu_id); + + pdd = kfd_process_device_data_by_id(process, gpu_id); + if (pdd) + dev = pdd->dev; switch (mmap_offset & KFD_MMAP_TYPE_MASK) { case KFD_MMAP_TYPE_DOORBELL: -- cgit v1.2.3 From d95429c1d4a994ba764b92ffb52afa488b98a8a7 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 9 Jul 2026 17:36:15 -0600 Subject: drm/amd/display: share common DM KUnit helpers Move fixture setup that was duplicated across several amdgpu_dm KUnit tests into amdgpu_dm_kunit_helpers.c: - dm_kunit_alloc_dc_with_ctx() allocates a dc and its dc_context. - dm_kunit_alloc_drm_with_connector_list() allocates a drm_device with an initialized connector list. Update the irq, psr, crtc and backlight tests to use these helpers. Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 9 +---- .../display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 30 +++-------------- .../display/amdgpu_dm/tests/amdgpu_dm_irq_test.c | 32 +++++------------- .../amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c | 39 +++++++++++++++++----- .../amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h | 4 +++ .../display/amdgpu_dm/tests/amdgpu_dm_psr_test.c | 15 ++------- 6 files changed, 52 insertions(+), 77 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index f55f93747df7..618fd5142da0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -56,14 +56,7 @@ static void setup_test_connector(struct kunit *test, static void setup_test_dm_ddev(struct kunit *test, struct amdgpu_display_manager *dm) { - struct drm_device *ddev; - - ddev = kunit_kzalloc(test, sizeof(*ddev), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ddev); - - INIT_LIST_HEAD(&ddev->mode_config.connector_list); - spin_lock_init(&ddev->mode_config.connector_list_lock); - dm->ddev = ddev; + dm->ddev = dm_kunit_alloc_drm_with_connector_list(test); } /* Tests for dm_find_stream_with_link() */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c index 0edaf969f16b..ac21bbec92ab 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -296,13 +296,9 @@ static void dm_test_crtc_is_headless_null_adev(struct kunit *test) static void dm_test_crtc_is_headless_no_connectors(struct kunit *test) { struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - - INIT_LIST_HEAD(&dev->mode_config.connector_list); - spin_lock_init(&dev->mode_config.connector_list_lock); adev->dm.ddev = dev; KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev)); @@ -315,15 +311,11 @@ static void dm_test_crtc_is_headless_no_connectors(struct kunit *test) static void dm_test_crtc_is_headless_writeback_only(struct kunit *test) { struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test); struct drm_connector *wb = kunit_kzalloc(test, sizeof(*wb), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb); - - INIT_LIST_HEAD(&dev->mode_config.connector_list); - spin_lock_init(&dev->mode_config.connector_list_lock); adev->dm.ddev = dev; dm_test_add_connector(dev, wb, DRM_MODE_CONNECTOR_WRITEBACK, @@ -339,15 +331,11 @@ static void dm_test_crtc_is_headless_writeback_only(struct kunit *test) static void dm_test_crtc_is_headless_disconnected_display(struct kunit *test) { struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test); struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display); - - INIT_LIST_HEAD(&dev->mode_config.connector_list); - spin_lock_init(&dev->mode_config.connector_list_lock); adev->dm.ddev = dev; dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_HDMIA, @@ -363,15 +351,11 @@ static void dm_test_crtc_is_headless_disconnected_display(struct kunit *test) static void dm_test_crtc_is_headless_connected_display(struct kunit *test) { struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test); struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display); - - INIT_LIST_HEAD(&dev->mode_config.connector_list); - spin_lock_init(&dev->mode_config.connector_list_lock); adev->dm.ddev = dev; dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_HDMIA, @@ -387,17 +371,13 @@ static void dm_test_crtc_is_headless_connected_display(struct kunit *test) static void dm_test_crtc_is_headless_mixed_connectors(struct kunit *test) { struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_device *dev = dm_kunit_alloc_drm_with_connector_list(test); struct drm_connector *wb = kunit_kzalloc(test, sizeof(*wb), GFP_KERNEL); struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display); - - INIT_LIST_HEAD(&dev->mode_config.connector_list); - spin_lock_init(&dev->mode_config.connector_list_lock); adev->dm.ddev = dev; dm_test_add_connector(dev, wb, DRM_MODE_CONNECTOR_WRITEBACK, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index dc7ef0523b8f..28c36217f6a2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -202,22 +202,6 @@ static void dm_test_dmub_notify_callback(struct amdgpu_device *adev, dm_test_dmub_notify_count++; } -static struct dc *dm_test_alloc_dc_with_ctx(struct kunit *test) -{ - struct dc_context *ctx; - struct dc *dc; - - dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); - ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); - - dc->ctx = ctx; - ctx->dc = dc; - - return dc; -} - static enum dc_irq_source dm_test_to_dal_irq_source_dce110( struct irq_service *irq_service, uint32_t src_id, @@ -299,7 +283,7 @@ static struct dc *dm_test_alloc_dc_with_irq_service(struct kunit *test, struct dc *dc; int i; - dc = dm_test_alloc_dc_with_ctx(test); + dc = dm_kunit_alloc_dc_with_ctx(test); res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool); irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); @@ -3283,7 +3267,7 @@ static void dm_test_register_hpd_handlers_dmub_outbox(struct kunit *test) adev = dm_kunit_alloc_adev(test); KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); - dc = dm_test_alloc_dc_with_ctx(test); + dc = dm_kunit_alloc_dc_with_ctx(test); adev->dm.dc = dc; /* Make dc_is_dmub_outbox_supported() return true. */ @@ -3495,7 +3479,7 @@ static void dm_test_dmub_outbox1_low_irq_empty(struct kunit *test) struct dc *dc; adev = dm_kunit_alloc_adev(test); - dc = dm_test_alloc_dc_with_ctx(test); + dc = dm_kunit_alloc_dc_with_ctx(test); dc_dmub_srv = kunit_kzalloc(test, sizeof(*dc_dmub_srv), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_dmub_srv); dmub = kunit_kzalloc(test, sizeof(*dmub), GFP_KERNEL); @@ -3529,7 +3513,7 @@ static struct amdgpu_device *dm_test_alloc_adev_outbox_notify(struct kunit *test struct dc *dc; adev = dm_kunit_alloc_adev(test); - dc = dm_test_alloc_dc_with_ctx(test); + dc = dm_kunit_alloc_dc_with_ctx(test); dc_dmub_srv = kunit_kzalloc(test, sizeof(*dc_dmub_srv), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_dmub_srv); dmub = kunit_kzalloc(test, sizeof(*dmub), GFP_KERNEL); @@ -3635,7 +3619,7 @@ static void dm_test_dce110_register_irq_handlers_rejects_uninitialized_sources(s struct dc *dc; adev = dm_kunit_alloc_adev(test); - dc = dm_test_alloc_dc_with_ctx(test); + dc = dm_kunit_alloc_dc_with_ctx(test); adev->dm.dc = dc; KUNIT_EXPECT_EQ(test, amdgpu_dm_dce110_register_irq_handlers(adev), -EINVAL); @@ -3696,7 +3680,7 @@ static void dm_test_dcn10_register_irq_handlers_zero_crtc(struct kunit *test) adev = dm_kunit_alloc_adev(test); KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, adev), 0); - dc = dm_test_alloc_dc_with_ctx(test); + dc = dm_kunit_alloc_dc_with_ctx(test); adev->dm.dc = dc; adev->mode_info.num_hpd = 1; amdgpu_dm_set_irq_funcs(adev); @@ -3759,7 +3743,7 @@ static void dm_test_register_outbox_irq_handlers_without_dmub(struct kunit *test adev = dm_kunit_alloc_adev(test); KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_free_irq_sources, adev), 0); - dc = dm_test_alloc_dc_with_ctx(test); + dc = dm_kunit_alloc_dc_with_ctx(test); adev->dm.dc = dc; amdgpu_dm_set_irq_funcs(adev); @@ -3899,7 +3883,7 @@ static void dm_test_handle_vmin_vmax_update(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); mutex_init(&adev->dm.dc_lock); - dc = dm_test_alloc_dc_with_ctx(test); + dc = dm_kunit_alloc_dc_with_ctx(test); dc->current_state = kunit_kzalloc(test, sizeof(*dc->current_state), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc->current_state); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c index 58615cdbe854..75cd4fef3076 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c @@ -45,23 +45,32 @@ struct dc_link *dm_kunit_alloc_link(struct kunit *test) } EXPORT_SYMBOL(dm_kunit_alloc_link); -struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test) +struct dc *dm_kunit_alloc_dc_with_ctx(struct kunit *test) { - struct dc_link *link; struct dc_context *ctx; struct dc *dc; - link = dm_kunit_alloc_link(test); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx); - dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dc); - - link->ctx = ctx; - ctx->dc = dc; dc->ctx = ctx; + ctx->dc = dc; + + return dc; +} +EXPORT_SYMBOL(dm_kunit_alloc_dc_with_ctx); + +struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test) +{ + struct dc_link *link; + struct dc *dc; + + link = dm_kunit_alloc_link(test); + dc = dm_kunit_alloc_dc_with_ctx(test); + link->ctx = dc->ctx; return link; } @@ -138,5 +147,19 @@ struct amdgpu_dm_connector *dm_kunit_alloc_connector(struct kunit *test, } EXPORT_SYMBOL(dm_kunit_alloc_connector); +struct drm_device *dm_kunit_alloc_drm_with_connector_list(struct kunit *test) +{ + struct drm_device *dev; + + dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + INIT_LIST_HEAD(&dev->mode_config.connector_list); + spin_lock_init(&dev->mode_config.connector_list_lock); + + return dev; +} +EXPORT_SYMBOL(dm_kunit_alloc_drm_with_connector_list); + MODULE_LICENSE("Dual MIT/GPL"); MODULE_DESCRIPTION("KUnit test helpers for amdgpu_dm tests"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h index 0f1c48fa2128..49b358681d60 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h @@ -13,14 +13,18 @@ struct amdgpu_device; struct amdgpu_display_manager; struct amdgpu_dm_connector; +struct dc; struct dc_link; struct dc_state; struct dc_stream_state; +struct drm_device; struct amdgpu_device *dm_kunit_alloc_adev(struct kunit *test); +struct dc *dm_kunit_alloc_dc_with_ctx(struct kunit *test); struct dc_link *dm_kunit_alloc_link(struct kunit *test); struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test); struct amdgpu_display_manager *dm_kunit_alloc_dm(struct kunit *test); +struct drm_device *dm_kunit_alloc_drm_with_connector_list(struct kunit *test); struct dc_stream_state *dm_kunit_alloc_stream(struct kunit *test, struct dc_link *link); void dm_kunit_add_stream_to_state(struct kunit *test, struct dc_state *state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c index 09bd98e93047..0a484ea7c0e8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c @@ -46,19 +46,10 @@ static struct core_power *create_test_power_module(struct kunit *test, static struct dc_link *alloc_test_psrsu_link(struct kunit *test) { - struct dc_link *link = dm_kunit_alloc_link(test); - struct dc_context *ctx; - struct dc *dc; - - ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx); - - dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dc); + struct dc_link *link = dm_kunit_alloc_link_with_ctx(test); + struct dc_context *ctx = link->ctx; + struct dc *dc = ctx->dc; - link->ctx = ctx; - ctx->dc = dc; - dc->ctx = ctx; dc->caps.dmcub_support = true; ctx->dce_version = DCN_VERSION_3_1; link->dpcd_caps.edp_rev = DP_EDP_14; -- cgit v1.2.3 From ed772ccfba62e962ab05227cf426036a25a9da70 Mon Sep 17 00:00:00 2001 From: John Harrison Date: Thu, 23 Jul 2026 16:35:48 -0700 Subject: drm/amd/display: Fix use-after-free bugs in KUnit tests When running the KUnit tests with memory debugging enabled, a bunch of use-after-free errors are hit. These are due to using KUnit managed memory for the test objects but then giving them over to DRM managed objects. The KUnit stuff is deallocated first and when the DRM side calls its clean up helpers, it hits memory that has been freed already. NB: There is still a use-after-free bug in the writeback tests. However, fixing that requires driver side changes as well as test side changes. Those changes are part of: https://patchwork.freedesktop.org/series/152420/ Signed-off-by: John Harrison CC: Alex Hung CC: Harry Wentland Reviewed-by: Alex Hung Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 37 +++++++++++----------- .../amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c | 3 +- .../display/amdgpu_dm/tests/amdgpu_dm_wb_test.c | 5 +-- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 75f6be829f24..2c7370f586a0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -1452,7 +1453,7 @@ static void dm_test_funcs_reset_sets_defaults(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_test_connector_funcs, @@ -1491,7 +1492,7 @@ static void dm_test_funcs_reset_edp_abm_level(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_test_connector_funcs, @@ -1529,7 +1530,7 @@ static void dm_test_funcs_reset_edp_abm_disabled(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_test_connector_funcs, @@ -1571,7 +1572,7 @@ static void dm_test_atomic_dup_state_copies_fields(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_test_connector_funcs, @@ -2362,8 +2363,8 @@ static void setup_panel_type_fixture(struct kunit *test, KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->drm); fixture->adev = drm_to_adev(fixture->drm); - fixture->aconnector = kunit_kzalloc(test, sizeof(*fixture->aconnector), - GFP_KERNEL); + fixture->aconnector = drmm_kzalloc(fixture->drm, sizeof(*fixture->aconnector), + GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->aconnector); fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->link); @@ -2557,9 +2558,9 @@ static void dm_test_update_subconnector_dp_with_sink(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + aconnector = drmm_kzalloc(drm, sizeof(*aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, aconnector); - link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + link = drmm_kzalloc(drm, sizeof(*link), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, link); drmm_connector_init(drm, &aconnector->base, &dm_test_connector_funcs, @@ -2601,9 +2602,9 @@ static void dm_test_update_subconnector_dp_no_sink(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + aconnector = drmm_kzalloc(drm, sizeof(*aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, aconnector); - link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + link = drmm_kzalloc(drm, sizeof(*link), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, link); drmm_connector_init(drm, &aconnector->base, &dm_test_connector_funcs, @@ -2644,9 +2645,9 @@ static void dm_test_update_subconnector_non_dp_noop(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + aconnector = drmm_kzalloc(drm, sizeof(*aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, aconnector); - link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + link = drmm_kzalloc(drm, sizeof(*link), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, link); drmm_connector_init(drm, &aconnector->base, &dm_test_connector_funcs, @@ -2660,7 +2661,7 @@ static void dm_test_update_subconnector_non_dp_noop(struct kunit *test) link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; aconnector->dc_link = link; - aconnector->dc_sink = kunit_kzalloc(test, sizeof(*aconnector->dc_sink), GFP_KERNEL); + aconnector->dc_sink = drmm_kzalloc(drm, sizeof(*aconnector->dc_sink), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, aconnector->dc_sink); update_subconnector_property(aconnector); @@ -2807,7 +2808,7 @@ static struct amdgpu_dm_connector *dm_test_add_connector(struct kunit *test, { struct amdgpu_dm_connector *aconnector; - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + aconnector = drmm_kzalloc(drm, sizeof(*aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, aconnector); KUNIT_ASSERT_EQ(test, @@ -3106,7 +3107,7 @@ static struct dm_test_panel_ctx *dm_test_panel_ctx_alloc(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, prop); ctx->drm->mode_config.panel_type_property = prop; - ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + ctx->aconnector = drmm_kzalloc(ctx->drm, sizeof(*ctx->aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); KUNIT_ASSERT_EQ(test, drmm_connector_init(ctx->drm, &ctx->aconnector->base, @@ -3115,7 +3116,7 @@ static struct dm_test_panel_ctx *dm_test_panel_ctx_alloc(struct kunit *test) drm_object_attach_property(&ctx->aconnector->base.base, prop, DRM_MODE_PANEL_TYPE_UNKNOWN); - ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); + ctx->link = drmm_kzalloc(ctx->drm, sizeof(*ctx->link), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->link); ctx->aconnector->dc_link = ctx->link; @@ -3379,14 +3380,14 @@ static struct dm_test_fill_ctx *dm_test_fill_ctx_alloc(struct kunit *test) DRIVER_MODESET); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->drm); - ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + ctx->aconnector = drmm_kzalloc(ctx->drm, sizeof(*ctx->aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); KUNIT_ASSERT_EQ(test, drmm_connector_init(ctx->drm, &ctx->aconnector->base, &dm_test_connector_funcs, DRM_MODE_CONNECTOR_DisplayPort, NULL), 0); - ctx->conn_state = kunit_kzalloc(test, sizeof(*ctx->conn_state), GFP_KERNEL); + ctx->conn_state = drmm_kzalloc(ctx->drm, sizeof(*ctx->conn_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state); ctx->stream = kunit_kzalloc(test, sizeof(*ctx->stream), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->stream); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c index 75cd4fef3076..862ada873fbb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "dc.h" #include "core_types.h" @@ -136,7 +137,7 @@ struct amdgpu_dm_connector *dm_kunit_alloc_connector(struct kunit *test, { struct amdgpu_dm_connector *aconnector; - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + aconnector = drmm_kzalloc(adev_to_drm(adev), sizeof(*aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, aconnector); if (adev) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c index b43bc244487e..3454cf371414 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -266,7 +267,7 @@ static void dm_test_wb_get_modes_returns_modes(struct kunit *test) DRIVER_MODESET | DRIVER_ATOMIC); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_wb_test_connector_funcs, @@ -301,7 +302,7 @@ static void dm_test_wb_get_modes_bounded_by_max(struct kunit *test) DRIVER_MODESET | DRIVER_ATOMIC); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + connector = drmm_kzalloc(drm, sizeof(*connector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, connector); drmm_connector_init(drm, connector, &dm_wb_test_connector_funcs, -- cgit v1.2.3 From e324cce52b49ea0ce687dfa01a0c6c79b7ed6e3f Mon Sep 17 00:00:00 2001 From: Dillon Varone Date: Fri, 10 Jul 2026 11:28:58 -0400 Subject: drm/amd/display: Port DCN4+ MCIF ARB programming to new format [WHY&HOW] DML2.1 now outputs MCIF arbiter programming, but using a new structure. Change DCN401 and DCN42 to use this new format. Reviewed-by: Sridevi Arvindekar Signed-off-by: Dillon Varone Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile | 10 ++ .../amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c | 4 +- .../amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h | 5 + .../display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c | 135 +++++++++++++++++++++ .../display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h | 40 ++++++ .../amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h | 1 + .../display/dc/resource/dcn401/dcn401_resource.c | 29 ++--- .../display/dc/resource/dcn401/dcn401_resource.h | 4 + .../amd/display/dc/resource/dcn42/dcn42_resource.c | 28 +++-- 9 files changed, 227 insertions(+), 29 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c create mode 100644 drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile b/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile index afc376defa0b..dddf5a9eb0b8 100644 --- a/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/Makefile @@ -50,6 +50,16 @@ MMHUBBUB_DCN35 = dcn35_mmhubbub.o AMD_DAL_MMHUBBUB_DCN35 = $(addprefix $(AMDDALPATH)/dc/mmhubbub/dcn35/,$(MMHUBBUB_DCN35)) AMD_DISPLAY_FILES += $(AMD_DAL_MMHUBBUB_DCN35) + + +############################################################################### +# DCN401 +############################################################################### +MMHUBBUB_DCN401 = dcn401_mmhubbub.o + +AMD_DAL_MMHUBBUB_DCN401 = $(addprefix $(AMDDALPATH)/dc/mmhubbub/dcn401/,$(MMHUBBUB_DCN401)) + +AMD_DISPLAY_FILES += $(AMD_DAL_MMHUBBUB_DCN401) endif ############################################################################### diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c index 6b6f80a3bfd5..5ce787632fe9 100644 --- a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.c @@ -73,7 +73,7 @@ * the bufmgr status can show the progress of write back, can be used for debug purpose */ -static void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb, +void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb, struct mcif_warmup_params *params) { struct dcn30_mmhubbub *mcif_wb30 = TO_DCN30_MMHUBBUB(mcif_wb); @@ -100,7 +100,7 @@ static void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb, REG_UPDATE(MMHUBBUB_WARMUP_CONTROL_STATUS, MMHUBBUB_WARMUP_EN, false); } -static void mmhubbub32_config_mcif_buf(struct mcif_wb *mcif_wb, +void mmhubbub32_config_mcif_buf(struct mcif_wb *mcif_wb, struct mcif_buf_params *params, unsigned int dest_height) { diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h index ef15b4f1f6b9..682f3bba6d50 100644 --- a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn32/dcn32_mmhubbub.h @@ -200,6 +200,11 @@ SF(MMHUBBUB_WARMUP_CONTROL_STATUS, MMHUBBUB_WARMUP_SW_INT_ACK, mask_sh),\ SF(MMHUBBUB_WARMUP_CONTROL_STATUS, MMHUBBUB_WARMUP_INC_ADDR, mask_sh) +void mmhubbub32_warmup_mcif(struct mcif_wb *mcif_wb, + struct mcif_warmup_params *params); +void mmhubbub32_config_mcif_buf(struct mcif_wb *mcif_wb, + struct mcif_buf_params *params, + unsigned int dest_height); void dcn32_mmhubbub_construct(struct dcn30_mmhubbub *mcif_wb30, struct dc_context *ctx, diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c new file mode 100644 index 000000000000..d2845acb74c8 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +// +// Copyright 2026 Advanced Micro Devices, Inc. + +#include "dcn401_mmhubbub.h" +#include "reg_helper.h" + +#define REG(reg) \ + ((const struct dcn35_mmhubbub_registers *)(mcif_wb30->mcif_wb_regs)) \ + ->reg + +#define CTX mcif_wb30->base.ctx + +#undef FN +#define FN(reg_name, field_name) \ + ((const struct dcn401_mmhubbub_shift *)(mcif_wb30->mcif_wb_shift)) \ + ->field_name, \ + ((const struct dcn401_mmhubbub_mask *)(mcif_wb30->mcif_wb_mask)) \ + ->field_name + +static void mmhubbub401_config_mcif_arb(struct mcif_wb *mcif_wb, + struct mcif_arb_params *params) +{ + struct dcn30_mmhubbub *mcif_wb30 = TO_DCN30_MMHUBBUB(mcif_wb); + + /* Programmed by the video driver based on the CRTC timing (for DWB) */ + REG_UPDATE(MCIF_WB_ARBITRATION_CONTROL, + MCIF_WB_TIME_PER_PIXEL, params->dcn4x.inst_regs.time_per_pixel); + + /* + * Programming DWB watermark. + * Watermark to generate urgent in MCIF_WB_CLI, value is determined by MCIF_WB_CLI_WATERMARK_MASK. + * Program in ns. A formula will be provided in the pseudo code to calculate the value. + */ + /* Program urgent_watermarkA */ + REG_UPDATE_2(MCIF_WB_WATERMARK, + MCIF_WB_CLI_WATERMARK_MASK, 0x0, + MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent); + /* Program urgent_watermarkB */ + REG_UPDATE_2(MCIF_WB_WATERMARK, + MCIF_WB_CLI_WATERMARK_MASK, 0x1, + MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent); + /* Program urgent_watermarkC */ + REG_UPDATE_2(MCIF_WB_WATERMARK, + MCIF_WB_CLI_WATERMARK_MASK, 0x2, + MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent); + /* Program urgent_watermarkD */ + REG_UPDATE_2(MCIF_WB_WATERMARK, + MCIF_WB_CLI_WATERMARK_MASK, 0x3, + MCIF_WB_CLI_WATERMARK, params->dcn4x.global_regs.wm_regs[0].urgent); + + /* Programming UCLK P-State watermark */ + /* Program nbp_state_change_watermarkA */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x0, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate); + /* Program nbp_state_change_watermarkB */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x1, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate); + /* Program nbp_state_change_watermarkC */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x2, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate); + /* Program nbp_state_change_watermarkD */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x3, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x0, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].uclk_pstate); + + /* Programming FCLK P-State watermark */ + /* Program nbp_state_change_watermarkA */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x0, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].temp_read_or_ppt); + /* Program nbp_state_change_watermarkB */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x1, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].temp_read_or_ppt); + /* Program nbp_state_change_watermarkC */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x2, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].fclk_pstate); + /* Program nbp_state_change_watermarkD */ + REG_UPDATE_3(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, + NB_PSTATE_CHANGE_WATERMARK_MASK, 0x3, + NB_PSTATE_CHANGE_WATERMARK_TYPE, 0x1, + NB_PSTATE_CHANGE_REFRESH_WATERMARK, params->dcn4x.global_regs.wm_regs[0].fclk_pstate); + + /* Program max_scaled_time */ + REG_UPDATE(MULTI_LEVEL_QOS_CTRL, + MAX_SCALED_TIME_TO_URGENT, params->dcn4x.inst_regs.max_scaled_time_ns); + + /* Program slice_lines */ + REG_UPDATE(MCIF_WB_BUFMGR_VCE_CONTROL, + MCIF_WB_BUFMGR_SLICE_SIZE, params->dcn4x.inst_regs.slice_lines); + + /* Set arbitration unit for Luma/Chroma */ + /* arb_unit=2 should be chosen for more efficiency */ + /* Arbitration size, 0: 2048 bytes 1: 4096 bytes 2: 8192 Bytes */ + REG_UPDATE(MCIF_WB_ARBITRATION_CONTROL, + MCIF_WB_CLIENT_ARBITRATION_SLICE, params->dcn4x.inst_regs.arbitration_slice); +} + +static const struct mcif_wb_funcs dcn401_mmhubbub_funcs = { + .warmup_mcif = mmhubbub32_warmup_mcif, + .enable_mcif = mmhubbub2_enable_mcif, + .disable_mcif = mmhubbub2_disable_mcif, + .config_mcif_buf = mmhubbub32_config_mcif_buf, + .config_mcif_arb = mmhubbub401_config_mcif_arb, + .config_mcif_irq = mmhubbub2_config_mcif_irq, + .dump_frame = mcifwb2_dump_frame, +}; + +void dcn401_mmhubbub_construct(struct dcn30_mmhubbub *mcif_wb30, + struct dc_context *ctx, + const struct dcn35_mmhubbub_registers *mcif_wb_regs, + const struct dcn401_mmhubbub_shift *mcif_wb_shift, + const struct dcn401_mmhubbub_mask *mcif_wb_mask, + int inst) +{ + mcif_wb30->base.ctx = ctx; + + mcif_wb30->base.inst = inst; + mcif_wb30->base.funcs = &dcn401_mmhubbub_funcs; + mcif_wb30->mcif_wb_regs = (const struct dcn30_mmhubbub_registers *)mcif_wb_regs; + mcif_wb30->mcif_wb_shift = (const struct dcn30_mmhubbub_shift *)mcif_wb_shift; + mcif_wb30->mcif_wb_mask = (const struct dcn30_mmhubbub_mask *)mcif_wb_mask; +} diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h new file mode 100644 index 000000000000..fbc69a210fa7 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn401/dcn401_mmhubbub.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright 2026 Advanced Micro Devices, Inc. */ + + +#ifndef __DCN401_MMHUBBUB_H +#define __DCN401_MMHUBBUB_H + +#include "mcif_wb.h" +#include "dcn32/dcn32_mmhubbub.h" +#include "dcn35/dcn35_mmhubbub.h" + +#define MCIF_WB_REG_VARIABLE_LIST_DCN4_01 \ + MCIF_WB_REG_VARIABLE_LIST_DCN3_5; + +#define MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(mask_sh) \ + MCIF_WB_COMMON_MASK_SH_LIST_DCN3_5(mask_sh), \ + SF(MCIF_WB_NB_PSTATE_LATENCY_WATERMARK, NB_PSTATE_CHANGE_WATERMARK_TYPE, mask_sh) + +#define MCIF_WB_REG_FIELD_LIST_DCN4_01(type) \ + struct { \ + MCIF_WB_REG_FIELD_LIST_DCN3_5(type); \ + type NB_PSTATE_CHANGE_WATERMARK_TYPE; \ + } + +struct dcn401_mmhubbub_mask { + MCIF_WB_REG_FIELD_LIST_DCN4_01(uint32_t); +}; + +struct dcn401_mmhubbub_shift { + MCIF_WB_REG_FIELD_LIST_DCN4_01(uint8_t); +}; + +void dcn401_mmhubbub_construct(struct dcn30_mmhubbub *mcif_wb30, + struct dc_context *ctx, + const struct dcn35_mmhubbub_registers *mcif_wb_regs, + const struct dcn401_mmhubbub_shift *mcif_wb_shift, + const struct dcn401_mmhubbub_mask *mcif_wb_mask, + int inst); + +#endif // __DCN401_MMHUBBUB_H diff --git a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h index 0dd1f0d2eb88..0001903a04ec 100644 --- a/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h +++ b/drivers/gpu/drm/amd/display/dc/mmhubbub/dcn42/dcn42_mmhubbub.h @@ -8,6 +8,7 @@ #include "mcif_wb.h" #include "dcn32/dcn32_mmhubbub.h" #include "dcn35/dcn35_mmhubbub.h" +#include "dcn401/dcn401_mmhubbub.h" void dcn42_mmhubbub_set_fgcg(struct dcn30_mmhubbub *mcif_wb30, bool enabled); #endif // __DCN42_MMHUBBUB_H diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c index e3ee792fb2e8..f7ba0509e6bd 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c @@ -16,6 +16,7 @@ #include "dcn30/dcn30_resource.h" #include "dcn32/dcn32_resource.h" #include "dcn321/dcn321_resource.h" +#include "dcn35/dcn35_resource.h" #include "dcn10/dcn10_ipp.h" #include "dcn401/dcn401_hubbub.h" @@ -58,7 +59,7 @@ #include "dcn31/dcn31_panel_cntl.h" #include "dcn30/dcn30_dwb.h" -#include "dcn32/dcn32_mmhubbub.h" +#include "dcn401/dcn401_mmhubbub.h" #include "dcn/dcn_4_1_0_offset.h" #include "dcn/dcn_4_1_0_sh_mask.h" @@ -435,17 +436,17 @@ static const struct dcn30_dwbc_mask dwbc401_mask = { }; -#define mcif_wb_regs_dcn3_init(id)\ - MCIF_WB_COMMON_REG_LIST_DCN32_RI(id) +#define mcif_wb_regs_dcn401_init(id) \ + MCIF_WB_COMMON_REG_LIST_DCN4_01_RI(id) -static struct dcn30_mmhubbub_registers mcif_wb30_regs[1]; +static struct dcn35_mmhubbub_registers mcif_wb401_regs[1]; -static const struct dcn30_mmhubbub_shift mcif_wb30_shift = { - MCIF_WB_COMMON_MASK_SH_LIST_DCN32(__SHIFT) +static const struct dcn401_mmhubbub_shift mcif_wb401_shift = { + MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(__SHIFT) }; -static const struct dcn30_mmhubbub_mask mcif_wb30_mask = { - MCIF_WB_COMMON_MASK_SH_LIST_DCN32(_MASK) +static const struct dcn401_mmhubbub_mask mcif_wb401_mask = { + MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(_MASK) }; #define dsc_regs_init(id)\ @@ -1704,13 +1705,13 @@ static bool dcn401_mmhubbub_create(struct dc_context *ctx, struct resource_pool } #undef REG_STRUCT -#define REG_STRUCT mcif_wb30_regs - mcif_wb_regs_dcn3_init(0); +#define REG_STRUCT mcif_wb401_regs + mcif_wb_regs_dcn401_init(0); - dcn32_mmhubbub_construct(mcif_wb30, ctx, - &mcif_wb30_regs[i], - &mcif_wb30_shift, - &mcif_wb30_mask, + dcn401_mmhubbub_construct(mcif_wb30, ctx, + &mcif_wb401_regs[i], + &mcif_wb401_shift, + &mcif_wb401_mask, i); pool->mcif_wb[i] = &mcif_wb30->base; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h index ce96fe1ddb7c..42a0986d6fe2 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h @@ -7,6 +7,7 @@ #include "core_types.h" #include "dcn32/dcn32_resource.h" +#include "dcn35/dcn35_resource.h" #include "dcn401/dcn401_hubp.h" #define TO_DCN401_RES_POOL(pool)\ @@ -660,4 +661,7 @@ int dcn401_get_power_profile(const struct dc_state *context); SR(SYMCLKC_CLOCK_ENABLE),\ SR(SYMCLKD_CLOCK_ENABLE) +#define MCIF_WB_COMMON_REG_LIST_DCN4_01_RI(inst) \ + MCIF_WB_COMMON_REG_LIST_DCN3_5_RI(inst) + #endif /* _DCN401_RESOURCE_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c index 547a0b816539..67673a9e5155 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c @@ -400,16 +400,18 @@ static const struct dcn30_dwbc_shift dwbc401_shift = { static const struct dcn30_dwbc_mask dwbc401_mask = { DWBC_COMMON_MASK_SH_LIST_DCN30(_MASK)}; -#define mcif_wb_regs_dcn3_init(id) \ - MCIF_WB_COMMON_REG_LIST_DCN3_5_RI(id) +#define mcif_wb_regs_dcn401_init(id) \ + MCIF_WB_COMMON_REG_LIST_DCN4_01_RI(id) -static struct dcn35_mmhubbub_registers mcif_wb35_regs[1]; +static struct dcn35_mmhubbub_registers mcif_wb401_regs[1]; -static const struct dcn35_mmhubbub_shift mcif_wb35_shift = { - MCIF_WB_COMMON_MASK_SH_LIST_DCN3_5(__SHIFT)}; +static const struct dcn401_mmhubbub_shift mcif_wb401_shift = { + MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(__SHIFT) +}; -static const struct dcn35_mmhubbub_mask mcif_wb35_mask = { - MCIF_WB_COMMON_MASK_SH_LIST_DCN3_5(_MASK)}; +static const struct dcn401_mmhubbub_mask mcif_wb401_mask = { + MCIF_WB_COMMON_MASK_SH_LIST_DCN4_01(_MASK) +}; #define dsc_regs_init(id) \ DSC_REG_LIST_DCN401_RI(id) @@ -1768,13 +1770,13 @@ static bool dcn42_mmhubbub_create(struct dc_context *ctx, struct resource_pool * } #undef REG_STRUCT -#define REG_STRUCT mcif_wb35_regs - mcif_wb_regs_dcn3_init(0); +#define REG_STRUCT mcif_wb401_regs + mcif_wb_regs_dcn401_init(0); - dcn35_mmhubbub_construct(mcif_wb30, ctx, - &mcif_wb35_regs[i], - &mcif_wb35_shift, - &mcif_wb35_mask, + dcn401_mmhubbub_construct(mcif_wb30, ctx, + &mcif_wb401_regs[i], + &mcif_wb401_shift, + &mcif_wb401_mask, i); dcn42_mmhubbub_init(mcif_wb30, ctx); -- cgit v1.2.3 From f73dd04acd0e8202870d40776b76a60df9ded776 Mon Sep 17 00:00:00 2001 From: Relja Vojvodic Date: Thu, 9 Jul 2026 13:56:53 -0400 Subject: drm/amd/display: Fix force FRL rate debug setting [Why & How] - force FRL rate debug bit was not correctly forcing requested FRL rate Reviewed-by: Chris Park Signed-off-by: Relja Vojvodic Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc_stream.c | 4 +++- drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c index 25f904eccb10..cdcf140bc1bb 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c @@ -100,7 +100,9 @@ void update_stream_signal(struct dc_stream_state *stream, struct dc_sink *sink) if (stream->link->frl_flags.force_frl_always || stream->link->frl_flags.force_frl_max - || stream->link->frl_flags.force_frl_dsc) + || stream->link->frl_flags.force_frl_dsc + || (stream->link->frl_flags.force_frl_rate != 0 && + stream->link->frl_flags.force_frl_rate != 0xF)) stream->signal = SIGNAL_TYPE_HDMI_FRL; } } diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c index c35074177a36..2afdfc113675 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c @@ -1090,7 +1090,7 @@ void hdmi_frl_decide_link_settings(struct dc_stream_state *stream, &stream->link->frl_verified_link_cap); if (stream->link->frl_flags.force_frl_rate != 0 && - stream->link->frl_flags.force_frl_rate < stream->link->frl_verified_link_cap.frl_link_rate) { + stream->link->frl_flags.force_frl_rate <= stream->link->frl_verified_link_cap.frl_link_rate) { switch (stream->link->frl_flags.force_frl_rate) { case HDMI_FRL_LINK_RATE_3GBPS: temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_3GBPS; -- cgit v1.2.3 From fad644eab118ec1ea8c4d27cb32d0986dcd98349 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Fri, 10 Jul 2026 14:05:41 -0400 Subject: drm/amd/display: Add KUnit tests for link_lock and psp SRM helpers Cover link_lock() across all links (lock then unlock) and the max_link == 0 no-op path, checking each per-link mutex ends in the expected state. Also cover the psp_get_srm() and psp_set_srm() guard paths when the HDCP TA context is uninitialized: psp_get_srm() returns NULL and psp_set_srm() returns -EINVAL, both leaving their output parameters untouched. The post-guard paths invoke real PSP firmware and are not unit-testable in UML. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 12 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 4 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 127 +++++++++++++++++++++ 3 files changed, 140 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 0dee603ecfaa..3b5e0e64adde 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -106,7 +106,8 @@ static bool lp_atomic_write_poll_read_aux( return dm_atomic_write_poll_read_aux(link, write, poll, read, poll_timeout_us, poll_mask_msb); } -static uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size) +STATIC_IFN_KUNIT +uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size) { struct ta_hdcp_shared_memory *hdcp_cmd; @@ -129,8 +130,10 @@ static uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint return hdcp_cmd->out_msg.hdcp_get_srm.srm_buf; } +EXPORT_IF_KUNIT(psp_get_srm); -static int psp_set_srm(struct psp_context *psp, +STATIC_IFN_KUNIT +int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version) { struct ta_hdcp_shared_memory *hdcp_cmd; @@ -157,6 +160,7 @@ static int psp_set_srm(struct psp_context *psp, *srm_version = hdcp_cmd->out_msg.hdcp_set_srm.srm_version; return 0; } +EXPORT_IF_KUNIT(psp_set_srm); STATIC_IFN_KUNIT void process_output(struct hdcp_workqueue *hdcp_work) @@ -245,7 +249,8 @@ void hdcp_get_link_display_adjustments( } EXPORT_IF_KUNIT(hdcp_get_link_display_adjustments); -static void link_lock(struct hdcp_workqueue *work, bool lock) +STATIC_IFN_KUNIT +void link_lock(struct hdcp_workqueue *work, bool lock) { int i = 0; @@ -256,6 +261,7 @@ static void link_lock(struct hdcp_workqueue *work, bool lock) mutex_unlock(&work[i].mutex); } } +EXPORT_IF_KUNIT(link_lock); STATIC_IFN_KUNIT void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index a82a20b80518..fb66f48d7297 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -44,6 +44,7 @@ struct mod_hdcp_link; struct mod_hdcp_display; struct cp_psp; struct amdgpu_device; +struct psp_context; struct hdcp_workqueue { struct work_struct cpirq_work; @@ -113,6 +114,9 @@ void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, unsigned int conn_index, bool enable_encryption); void event_property_update(struct work_struct *work); +void link_lock(struct hdcp_workqueue *work, bool lock); +uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size); +int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version); #endif #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index dbcff92672e6..43c7efcdd22c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -8,6 +8,7 @@ #include #include +#include "amdgpu.h" #include "amdgpu_dm_hdcp.h" static void dummy_work_fn(struct work_struct *work) {} @@ -632,6 +633,126 @@ static void dm_test_hdcp_create_workqueue_zero_max_links_returns_null(struct kun /* End of tests for hdcp_create_workqueue() */ +/* Tests for link_lock() */ + +/** + * dm_test_link_lock_locks_and_unlocks_all_links - lock/unlock spans every link + * @test: KUnit test context + * + * link_lock() should acquire the mutex of every entry from 0 to max_link + * when locking, and release all of them when unlocking. A subsequent + * lock/unlock cycle must succeed, proving the mutexes were left released. + */ +static void dm_test_link_lock_locks_and_unlocks_all_links(struct kunit *test) +{ + const int num_links = 3; + struct hdcp_workqueue *work; + int i; + + work = kunit_kcalloc(test, num_links, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + /* max_link is read from the first element. */ + work[0].max_link = num_links; + for (i = 0; i < num_links; i++) + mutex_init(&work[i].mutex); + + link_lock(work, true); + for (i = 0; i < num_links; i++) + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&work[i].mutex)); + + link_lock(work, false); + for (i = 0; i < num_links; i++) + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work[i].mutex)); + + /* Mutexes must be re-acquirable after being released. */ + link_lock(work, true); + for (i = 0; i < num_links; i++) + KUNIT_EXPECT_TRUE(test, mutex_is_locked(&work[i].mutex)); + link_lock(work, false); +} + +/** + * dm_test_link_lock_zero_links_is_noop - zero max_link touches no mutexes + * @test: KUnit test context + * + * When max_link is zero, link_lock() must not touch any mutex and simply + * return, leaving the (single) entry's mutex unlocked. + */ +static void dm_test_link_lock_zero_links_is_noop(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + work->max_link = 0; + + link_lock(work, true); + + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex)); +} + +/* End of tests for link_lock() */ + +/* Tests for psp_get_srm() and psp_set_srm() */ + +/** + * dm_test_psp_get_srm_uninitialized_returns_null - GET fails when TA not initialized + * @test: KUnit test context + * + * When the HDCP TA context is not initialized, psp_get_srm() must take the + * guard path and return NULL without touching the output parameters or + * invoking the (real) firmware path. + */ +static void dm_test_psp_get_srm_uninitialized_returns_null(struct kunit *test) +{ + struct psp_context *psp; + uint32_t srm_version = 0xdead; + uint32_t srm_size = 0xbeef; + uint8_t *srm; + + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + + /* kzalloc leaves hdcp_context.context.initialized == false */ + srm = psp_get_srm(psp, &srm_version, &srm_size); + + KUNIT_EXPECT_PTR_EQ(test, srm, NULL); + /* Output parameters must be left untouched on the guard path. */ + KUNIT_EXPECT_EQ(test, srm_version, 0xdead); + KUNIT_EXPECT_EQ(test, srm_size, 0xbeef); +} + +/** + * dm_test_psp_set_srm_uninitialized_returns_einval - SET fails when TA not initialized + * @test: KUnit test context + * + * When the HDCP TA context is not initialized, psp_set_srm() must take the + * guard path and return -EINVAL without updating srm_version or invoking + * the (real) firmware path. + */ +static void dm_test_psp_set_srm_uninitialized_returns_einval(struct kunit *test) +{ + struct psp_context *psp; + uint32_t srm_version = 0xdead; + u8 srm_buf[4] = {0}; + int ret; + + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + + /* kzalloc leaves hdcp_context.context.initialized == false */ + ret = psp_set_srm(psp, srm_buf, sizeof(srm_buf), &srm_version); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + /* srm_version must be left untouched on the guard path. */ + KUNIT_EXPECT_EQ(test, srm_version, 0xdead); +} + +/* End of tests for psp_get_srm() and psp_set_srm() */ + static struct kunit_case dm_hdcp_test_cases[] = { /* hdcp_get_content_protection_from_status() */ KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired), @@ -663,6 +784,12 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate), /* hdcp_create_workqueue() */ KUNIT_CASE(dm_test_hdcp_create_workqueue_zero_max_links_returns_null), + /* link_lock() */ + KUNIT_CASE(dm_test_link_lock_locks_and_unlocks_all_links), + KUNIT_CASE(dm_test_link_lock_zero_links_is_noop), + /* psp_get_srm() / psp_set_srm() */ + KUNIT_CASE(dm_test_psp_get_srm_uninitialized_returns_null), + KUNIT_CASE(dm_test_psp_set_srm_uninitialized_returns_einval), {} }; -- cgit v1.2.3 From 2ed4cb8d26158e7472d923880ff875960129e9ad Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Fri, 10 Jul 2026 14:26:01 -0400 Subject: drm/amd/display: Add KUnit tests for HDCP display helpers Cover the HDCP display lifecycle helpers: - hdcp_update_display() registers the connector and, on the disable path, resets the per-link encryption status to HDCP_OFF. - hdcp_remove_display() reverts an ENABLED connector back to DESIRED and clears the per-link entry, including the NULL connector-state path. - hdcp_reset_display() clears the encryption status and the connector for every link. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 6 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 2 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 207 +++++++++++++++++++++ 3 files changed, 214 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 3b5e0e64adde..f786c0a85425 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -317,8 +317,10 @@ void hdcp_update_display(struct hdcp_workqueue *hdcp_work, process_output(hdcp_w); } +EXPORT_IF_KUNIT(hdcp_update_display); -static void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, +STATIC_IFN_KUNIT +void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index, struct amdgpu_dm_connector *aconnector) { @@ -348,6 +350,7 @@ static void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, } process_output(hdcp_w); } +EXPORT_IF_KUNIT(hdcp_remove_display); void hdcp_reset_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index) { @@ -371,6 +374,7 @@ void hdcp_reset_display(struct hdcp_workqueue *hdcp_work, unsigned int link_inde process_output(hdcp_w); } +EXPORT_IF_KUNIT(hdcp_reset_display); void hdcp_handle_cpirq(struct hdcp_workqueue *hdcp_work, unsigned int link_index) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index fb66f48d7297..69a50e081c56 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -115,6 +115,8 @@ void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, bool enable_encryption); void event_property_update(struct work_struct *work); void link_lock(struct hdcp_workqueue *work, bool lock); +void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index, + struct amdgpu_dm_connector *aconnector); uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size); int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version); #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index 43c7efcdd22c..e6d03199187b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -9,6 +9,7 @@ #include #include "amdgpu.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_hdcp.h" static void dummy_work_fn(struct work_struct *work) {} @@ -753,6 +754,206 @@ static void dm_test_psp_set_srm_uninitialized_returns_einval(struct kunit *test) /* End of tests for psp_get_srm() and psp_set_srm() */ +/* + * Tests for hdcp_update_display() / hdcp_remove_display() / + * hdcp_reset_display(). + */ + +/** + * alloc_test_workqueue_locked - workqueue with dworks and mutex initialised + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with its mutex and the three delayed + * works initialised, as required by the guard(mutex) and process_output() + * usage in the display update/remove/reset helpers. + */ +static struct hdcp_workqueue *alloc_test_workqueue_locked(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + + return work; +} + +/** + * alloc_test_connector - minimal amdgpu_dm_connector for display tests + * @test: KUnit test context for managed allocation + * @index: drm connector index to assign + * + * Allocates an amdgpu_dm_connector with an initialised connector refcount + * (so drm_connector_get() is valid) and a dc_link/dc pair. The connector's + * free_cb is left NULL so drm_connector_put() is a safe no-op that never + * releases the object. + */ +static struct amdgpu_dm_connector *alloc_test_connector(struct kunit *test, + unsigned int index) +{ + struct amdgpu_dm_connector *aconnector; + struct dc_link *dc_link; + struct dc *dc; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + dc_link = kunit_kzalloc(test, sizeof(*dc_link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_link); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + kref_init(&aconnector->base.base.refcount); + aconnector->base.index = index; + dc_link->dc = dc; + aconnector->dc_link = dc_link; + + return aconnector; +} + +/** + * dm_test_hdcp_update_display_enable_registers_connector - enable stores the connector + * @test: KUnit test context + * + * hdcp_update_display() with enable_encryption=true should register the + * connector in the per-link aconnector array at its connector index. The + * hdcp has no active display, so mod_hdcp_update_display() is effectively a + * no-op and the call must not touch encryption_status. + */ +static void dm_test_hdcp_update_display_enable_registers_connector(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + + work->srm_size = 0; + + hdcp_update_display(work, 0, aconnector, DRM_MODE_HDCP_CONTENT_TYPE0, true); + + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], aconnector); + KUNIT_EXPECT_EQ(test, work->encryption_status[0], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/** + * dm_test_hdcp_update_display_disable_sets_status_off - disable clears status + * @test: KUnit test context + * + * hdcp_update_display() with enable_encryption=false should register the + * connector and reset the connector's encryption_status entry to HDCP_OFF + * via hdcp_update_display_encryption_control(). + */ +static void dm_test_hdcp_update_display_disable_sets_status_off(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 2); + + work->encryption_status[2] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + hdcp_update_display(work, 0, aconnector, DRM_MODE_HDCP_CONTENT_TYPE0, false); + + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[2], aconnector); + KUNIT_EXPECT_EQ(test, work->encryption_status[2], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/** + * dm_test_hdcp_remove_display_enabled_resets_cp - ENABLED CP reverts to DESIRED + * @test: KUnit test context + * + * hdcp_remove_display() must revert a connector whose content_protection is + * ENABLED back to DESIRED and clear the per-link aconnector entry. + */ +static void dm_test_hdcp_remove_display_enabled_resets_cp(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 1); + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + aconnector->base.state = conn_state; + work->aconnector[1] = aconnector; + + hdcp_remove_display(work, 0, aconnector); + + KUNIT_EXPECT_EQ(test, conn_state->content_protection, + DRM_MODE_CONTENT_PROTECTION_DESIRED); + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[1], NULL); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/** + * dm_test_hdcp_remove_display_null_state_clears_connector - NULL state skips CP change + * @test: KUnit test context + * + * When the connector has no drm_connector_state, hdcp_remove_display() must + * skip the content_protection update path and still clear the per-link + * aconnector entry. + */ +static void dm_test_hdcp_remove_display_null_state_clears_connector(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + + aconnector->base.state = NULL; + work->aconnector[0] = aconnector; + + hdcp_remove_display(work, 0, aconnector); + + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/** + * dm_test_hdcp_reset_display_clears_all_state - reset clears status and connectors + * @test: KUnit test context + * + * hdcp_reset_display() must reset every connector's encryption_status to + * HDCP_OFF and clear all per-link aconnector entries. + */ +static void dm_test_hdcp_reset_display_clears_all_state(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 4); + + work->encryption_status[4] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + work->aconnector[4] = aconnector; + + hdcp_reset_display(work, 0); + + KUNIT_EXPECT_EQ(test, work->encryption_status[4], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[4], NULL); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/* + * End of tests for hdcp_update_display() / hdcp_remove_display() / + * hdcp_reset_display(). + */ + static struct kunit_case dm_hdcp_test_cases[] = { /* hdcp_get_content_protection_from_status() */ KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired), @@ -790,6 +991,12 @@ static struct kunit_case dm_hdcp_test_cases[] = { /* psp_get_srm() / psp_set_srm() */ KUNIT_CASE(dm_test_psp_get_srm_uninitialized_returns_null), KUNIT_CASE(dm_test_psp_set_srm_uninitialized_returns_einval), + /* hdcp_update_display() / hdcp_remove_display() / hdcp_reset_display() */ + KUNIT_CASE(dm_test_hdcp_update_display_enable_registers_connector), + KUNIT_CASE(dm_test_hdcp_update_display_disable_sets_status_off), + KUNIT_CASE(dm_test_hdcp_remove_display_enabled_resets_cp), + KUNIT_CASE(dm_test_hdcp_remove_display_null_state_clears_connector), + KUNIT_CASE(dm_test_hdcp_reset_display_clears_all_state), {} }; -- cgit v1.2.3 From 700015a1464a81dbc0795d11e6259edb07136dee Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Fri, 10 Jul 2026 15:19:05 -0400 Subject: drm/amd/display: Add KUnit tests for event_callback Verify event_callback() cancels a pending callback_dwork and then runs process_output(), which re-arms property_validate_dwork, and that the work-queue mutex is released on return. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 4 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 78 ++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index f786c0a85425..34f70e517dc6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -384,7 +384,8 @@ void hdcp_handle_cpirq(struct hdcp_workqueue *hdcp_work, unsigned int link_index } EXPORT_IF_KUNIT(hdcp_handle_cpirq); -static void event_callback(struct work_struct *work) +STATIC_IFN_KUNIT +void event_callback(struct work_struct *work) { struct hdcp_workqueue *hdcp_work; @@ -400,6 +401,7 @@ static void event_callback(struct work_struct *work) process_output(hdcp_work); } +EXPORT_IF_KUNIT(event_callback); STATIC_IFN_KUNIT void event_property_update(struct work_struct *work) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index 69a50e081c56..4b8ba743f9d8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -114,6 +114,7 @@ void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, unsigned int conn_index, bool enable_encryption); void event_property_update(struct work_struct *work); +void event_callback(struct work_struct *work); void link_lock(struct hdcp_workqueue *work, bool lock); void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index, struct amdgpu_dm_connector *aconnector); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index e6d03199187b..e0386030b35e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -501,6 +501,81 @@ static void dm_test_event_property_update_skips_null_connector(struct kunit *tes /* End of tests for event_property_update() */ +/* Tests for event_callback() */ + +/** + * alloc_test_workqueue_for_callback - workqueue ready for event_callback() + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with its mutex and the three delayed + * works initialised, as required by the guard(mutex), cancel_delayed_work() + * and process_output() usage inside event_callback(). + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_callback(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + + return work; +} + +/** + * dm_test_event_callback_cancels_callback_dwork - callback work is cancelled + * @test: KUnit test context + * + * event_callback() must cancel a previously scheduled callback_dwork. With + * no active hdcp display, mod_hdcp_process_event() leaves output cleared so + * the callback is not requeued and callback_dwork ends up not pending. + */ +static void dm_test_event_callback_cancels_callback_dwork(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test); + + /* Pre-schedule callback_dwork with a long delay so it won't fire. */ + schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + event_callback(&work->callback_dwork.work); + + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_event_callback_schedules_property_validate - process_output() runs + * @test: KUnit test context + * + * event_callback() finishes by calling process_output(), which always + * enqueues property_validate_dwork with delay=0. Verifying it is pending + * proves event_callback() reached process_output() and released the mutex. + */ +static void dm_test_event_callback_schedules_property_validate(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test); + + event_callback(&work->callback_dwork.work); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work)); + /* Mutex must be released after the guard scope exits. */ + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* End of tests for event_callback() */ + /* Tests for hdcp_handle_cpirq() */ /** @@ -977,6 +1052,9 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_process_output_watchdog_stop_and_needed_requeues), /* event_property_update() */ KUNIT_CASE(dm_test_event_property_update_skips_null_connector), + /* event_callback() */ + KUNIT_CASE(dm_test_event_callback_cancels_callback_dwork), + KUNIT_CASE(dm_test_event_callback_schedules_property_validate), /* hdcp_handle_cpirq() */ KUNIT_CASE(dm_test_hdcp_handle_cpirq_schedules_work), KUNIT_CASE(dm_test_hdcp_handle_cpirq_selects_link_index), -- cgit v1.2.3 From 5e1960769934a1515cc5e25aa226b376c0972164 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Fri, 10 Jul 2026 15:35:49 -0400 Subject: drm/amd/display: Add KUnit tests for event_property_validate Cover the per-connector scan in event_property_validate(): NULL, disconnected and NULL-state connectors are skipped; a changed encryption status updates the cached value and schedules property_update_work; and an unchanged status leaves it untouched. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 4 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 178 +++++++++++++++++++++ 3 files changed, 182 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 34f70e517dc6..622d9c275c3a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -465,7 +465,8 @@ void event_property_update(struct work_struct *work) } EXPORT_IF_KUNIT(event_property_update); -static void event_property_validate(struct work_struct *work) +STATIC_IFN_KUNIT +void event_property_validate(struct work_struct *work) { struct hdcp_workqueue *hdcp_work = container_of(to_delayed_work(work), struct hdcp_workqueue, property_validate_dwork); @@ -514,6 +515,7 @@ static void event_property_validate(struct work_struct *work) } } } +EXPORT_IF_KUNIT(event_property_validate); static void event_watchdog_timer(struct work_struct *work) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index 4b8ba743f9d8..57c534efbc71 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -114,6 +114,7 @@ void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, unsigned int conn_index, bool enable_encryption); void event_property_update(struct work_struct *work); +void event_property_validate(struct work_struct *work); void event_callback(struct work_struct *work); void link_lock(struct hdcp_workqueue *work, bool lock); void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index e0386030b35e..6f5cae0960e1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -576,6 +576,178 @@ static void dm_test_event_callback_schedules_property_validate(struct kunit *tes /* End of tests for event_callback() */ +/* Tests for event_property_validate() */ + +/** + * alloc_test_workqueue_for_property_validate - workqueue for validate tests + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with its mutex and property_update_work + * initialised, as required by the guard(mutex) and schedule_work() usage + * inside event_property_validate(). + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_property_validate(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + INIT_WORK(&work->property_update_work, dummy_work_fn); + + return work; +} + +/** + * alloc_validate_connector - connector for event_property_validate() tests + * @test: KUnit test context for managed allocation + * @index: drm connector index to assign + * @status: drm connector detection status to assign + * @with_state: whether to attach a zeroed drm_connector_state + * + * Allocates an amdgpu_dm_connector sufficient for the traversal in + * event_property_validate(). mod_hdcp_query_display() has no active display + * so it returns early, leaving the pre-set HDCP_OFF query untouched. + */ +static struct amdgpu_dm_connector *alloc_validate_connector(struct kunit *test, + unsigned int index, + enum drm_connector_status status, + bool with_state) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.index = index; + aconnector->base.status = status; + + if (with_state) { + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + aconnector->base.state = conn_state; + } + + return aconnector; +} + +/** + * dm_test_event_property_validate_skips_null_connector - null entries ignored + * @test: KUnit test context + * + * With every aconnector entry NULL, event_property_validate() must not + * schedule property_update_work and must leave encryption_status untouched. + */ +static void dm_test_event_property_validate_skips_null_connector(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test); + + work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_validate(&work->property_validate_dwork.work); + + KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work)); + KUNIT_EXPECT_EQ(test, work->encryption_status[0], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_validate_skips_disconnected - disconnected is skipped + * @test: KUnit test context + * + * A connector whose status is not connector_status_connected must be + * skipped, leaving encryption_status unchanged and no work scheduled. + */ +static void dm_test_event_property_validate_skips_disconnected(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test); + + work->aconnector[1] = alloc_validate_connector(test, 1, + connector_status_disconnected, true); + work->encryption_status[1] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_validate(&work->property_validate_dwork.work); + + KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work)); + KUNIT_EXPECT_EQ(test, work->encryption_status[1], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_validate_skips_null_state - missing state is skipped + * @test: KUnit test context + * + * A connected connector without a drm_connector_state must be skipped + * before the query, leaving encryption_status unchanged. + */ +static void dm_test_event_property_validate_skips_null_state(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test); + + work->aconnector[2] = alloc_validate_connector(test, 2, + connector_status_connected, false); + work->encryption_status[2] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_validate(&work->property_validate_dwork.work); + + KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work)); + KUNIT_EXPECT_EQ(test, work->encryption_status[2], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_validate_updates_on_status_change - change triggers update + * @test: KUnit test context + * + * For a connected connector with state, the query returns HDCP_OFF (no + * active hdcp display). When the stored encryption_status differs, it must + * be updated to HDCP_OFF and property_update_work must be scheduled. + */ +static void dm_test_event_property_validate_updates_on_status_change(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test); + + work->aconnector[3] = alloc_validate_connector(test, 3, + connector_status_connected, true); + work->encryption_status[3] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_validate(&work->property_validate_dwork.work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[3], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_update_work)); + + cancel_work_sync(&work->property_update_work); +} + +/** + * dm_test_event_property_validate_no_update_when_unchanged - no change, no work + * @test: KUnit test context + * + * For a connected connector with state whose stored encryption_status is + * already HDCP_OFF (matching the query result), event_property_validate() + * must not reschedule property_update_work. + */ +static void dm_test_event_property_validate_no_update_when_unchanged(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_validate(test); + + work->aconnector[4] = alloc_validate_connector(test, 4, + connector_status_connected, true); + work->encryption_status[4] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; + + event_property_validate(&work->property_validate_dwork.work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[4], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_FALSE(test, work_pending(&work->property_update_work)); +} + +/* End of tests for event_property_validate() */ + /* Tests for hdcp_handle_cpirq() */ /** @@ -1055,6 +1227,12 @@ static struct kunit_case dm_hdcp_test_cases[] = { /* event_callback() */ KUNIT_CASE(dm_test_event_callback_cancels_callback_dwork), KUNIT_CASE(dm_test_event_callback_schedules_property_validate), + /* event_property_validate() */ + KUNIT_CASE(dm_test_event_property_validate_skips_null_connector), + KUNIT_CASE(dm_test_event_property_validate_skips_disconnected), + KUNIT_CASE(dm_test_event_property_validate_skips_null_state), + KUNIT_CASE(dm_test_event_property_validate_updates_on_status_change), + KUNIT_CASE(dm_test_event_property_validate_no_update_when_unchanged), /* hdcp_handle_cpirq() */ KUNIT_CASE(dm_test_hdcp_handle_cpirq_schedules_work), KUNIT_CASE(dm_test_hdcp_handle_cpirq_selects_link_index), -- cgit v1.2.3 From 560ef68fec09a6fa92539eb99976e020e26148c6 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Fri, 10 Jul 2026 15:58:13 -0400 Subject: drm/amd/display: Add KUnit tests for watchdog and cpirq events Verify event_watchdog_timer() cancels its pending watchdog_dwork and runs process_output(), and that event_cpirq() runs process_output() without arming the callback or watchdog timers. Both release the work-queue mutex on return. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 8 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 2 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 132 +++++++++++++++++++++ 3 files changed, 140 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 622d9c275c3a..896668c92888 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -517,7 +517,8 @@ void event_property_validate(struct work_struct *work) } EXPORT_IF_KUNIT(event_property_validate); -static void event_watchdog_timer(struct work_struct *work) +STATIC_IFN_KUNIT +void event_watchdog_timer(struct work_struct *work) { struct hdcp_workqueue *hdcp_work; @@ -535,8 +536,10 @@ static void event_watchdog_timer(struct work_struct *work) process_output(hdcp_work); } +EXPORT_IF_KUNIT(event_watchdog_timer); -static void event_cpirq(struct work_struct *work) +STATIC_IFN_KUNIT +void event_cpirq(struct work_struct *work) { struct hdcp_workqueue *hdcp_work; @@ -548,6 +551,7 @@ static void event_cpirq(struct work_struct *work) process_output(hdcp_work); } +EXPORT_IF_KUNIT(event_cpirq); void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *hdcp_work) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index 57c534efbc71..a2b08bec58b5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -116,6 +116,8 @@ void hdcp_update_display_encryption_control(struct hdcp_workqueue *hdcp_work, void event_property_update(struct work_struct *work); void event_property_validate(struct work_struct *work); void event_callback(struct work_struct *work); +void event_watchdog_timer(struct work_struct *work); +void event_cpirq(struct work_struct *work); void link_lock(struct hdcp_workqueue *work, bool lock); void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index, struct amdgpu_dm_connector *aconnector); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index 6f5cae0960e1..a4097a4aec26 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -748,6 +748,132 @@ static void dm_test_event_property_validate_no_update_when_unchanged(struct kuni /* End of tests for event_property_validate() */ +/* Tests for event_watchdog_timer() */ + +/** + * dm_test_event_watchdog_timer_cancels_watchdog_dwork - watchdog work is cancelled + * @test: KUnit test context + * + * event_watchdog_timer() must cancel a previously scheduled + * watchdog_timer_dwork. With no active hdcp display, + * mod_hdcp_process_event() leaves output cleared so the watchdog is not + * requeued and watchdog_timer_dwork ends up not pending. + */ +static void dm_test_event_watchdog_timer_cancels_watchdog_dwork(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test); + + /* Pre-schedule watchdog_timer_dwork with a long delay so it won't fire. */ + schedule_delayed_work(&work->watchdog_timer_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + event_watchdog_timer(&work->watchdog_timer_dwork.work); + + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_event_watchdog_timer_schedules_property_validate - process_output() runs + * @test: KUnit test context + * + * event_watchdog_timer() finishes by calling process_output(), which always + * enqueues property_validate_dwork with delay=0. Verifying it is pending + * proves the handler reached process_output() and released the mutex. + */ +static void dm_test_event_watchdog_timer_schedules_property_validate(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_callback(test); + + event_watchdog_timer(&work->watchdog_timer_dwork.work); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work)); + /* Mutex must be released after the guard scope exits. */ + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* End of tests for event_watchdog_timer() */ + +/* Tests for event_cpirq() */ + +/** + * alloc_test_workqueue_for_cpirq - workqueue ready for event_cpirq() + * @test: KUnit test context for managed allocation + * + * Allocates a minimal hdcp_workqueue with its mutex, cpirq_work and the + * three delayed works initialised, as required by the guard(mutex) and + * process_output() usage inside event_cpirq(). + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_cpirq(struct kunit *test) +{ + struct hdcp_workqueue *work; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + mutex_init(&work->mutex); + INIT_WORK(&work->cpirq_work, dummy_work_fn); + INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + + return work; +} + +/** + * dm_test_event_cpirq_schedules_property_validate - process_output() runs + * @test: KUnit test context + * + * event_cpirq() finishes by calling process_output(), which always + * enqueues property_validate_dwork with delay=0. Verifying it is pending + * proves the handler reached process_output() and released the mutex. + */ +static void dm_test_event_cpirq_schedules_property_validate(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_cpirq(test); + + event_cpirq(&work->cpirq_work); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work)); + /* Mutex must be released after the guard scope exits. */ + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&work->mutex)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/** + * dm_test_event_cpirq_leaves_callback_and_watchdog_idle - only validate is queued + * @test: KUnit test context + * + * With no active hdcp display, mod_hdcp_process_event() leaves output + * cleared, so event_cpirq() must not schedule callback_dwork or + * watchdog_timer_dwork; only property_validate_dwork is enqueued. + */ +static void dm_test_event_cpirq_leaves_callback_and_watchdog_idle(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_cpirq(test); + + event_cpirq(&work->cpirq_work); + + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork)); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork)); + + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); + cancel_delayed_work_sync(&work->property_validate_dwork); +} + +/* End of tests for event_cpirq() */ + /* Tests for hdcp_handle_cpirq() */ /** @@ -1233,6 +1359,12 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_event_property_validate_skips_null_state), KUNIT_CASE(dm_test_event_property_validate_updates_on_status_change), KUNIT_CASE(dm_test_event_property_validate_no_update_when_unchanged), + /* event_watchdog_timer() */ + KUNIT_CASE(dm_test_event_watchdog_timer_cancels_watchdog_dwork), + KUNIT_CASE(dm_test_event_watchdog_timer_schedules_property_validate), + /* event_cpirq() */ + KUNIT_CASE(dm_test_event_cpirq_schedules_property_validate), + KUNIT_CASE(dm_test_event_cpirq_leaves_callback_and_watchdog_idle), /* hdcp_handle_cpirq() */ KUNIT_CASE(dm_test_hdcp_handle_cpirq_schedules_work), KUNIT_CASE(dm_test_hdcp_handle_cpirq_selects_link_index), -- cgit v1.2.3 From 3c729afe2f9a319e0852de9af460f94d1207ce1a Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Fri, 10 Jul 2026 16:12:55 -0400 Subject: drm/amd/display: Add KUnit tests for hdcp_destroy Verify hdcp_destroy() cancels each link's delayed works, removes the SRM sysfs file and frees the workqueue and SRM buffers. Cover both a populated work-queue and the zero-link / NULL-SRM path. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 122 +++++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 896668c92888..e704b6b7fc0b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -568,6 +568,7 @@ void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *hdcp_work) kfree(hdcp_work->srm_temp); kfree(hdcp_work); } +EXPORT_IF_KUNIT(hdcp_destroy); static bool enable_assr(void *handle, struct dc_link *link) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index a4097a4aec26..5eace7fa4100 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -7,6 +7,9 @@ #include #include +#include +#include +#include #include "amdgpu.h" #include "amdgpu_dm.h" @@ -1007,6 +1010,122 @@ static void dm_test_hdcp_create_workqueue_zero_max_links_returns_null(struct kun /* End of tests for hdcp_create_workqueue() */ +/* Tests for hdcp_destroy() */ + +static ssize_t test_srm_bin_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buffer, + loff_t pos, size_t count) +{ + return 0; +} + +static ssize_t test_srm_bin_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buffer, + loff_t pos, size_t count) +{ + return count; +} + +/** + * setup_destroy_sysfs - create a kobject with the SRM bin file attached + * @test: KUnit test context + * @work: workqueue whose attr will be registered + * + * hdcp_destroy() calls sysfs_remove_bin_file() on the first entry's attr, so + * a real kobject with the bin file created is required. Returns the kobject, + * which the caller must kobject_put() after hdcp_destroy() has run. + */ +static struct kobject *setup_destroy_sysfs(struct kunit *test, + struct hdcp_workqueue *work) +{ + struct kobject *kobj; + int ret; + + kobj = kobject_create_and_add("amdgpu_dm_hdcp_test", NULL); + KUNIT_ASSERT_NOT_NULL(test, kobj); + + sysfs_bin_attr_init(&work->attr); + work->attr.attr.name = "hdcp_srm"; + work->attr.attr.mode = 0664; + work->attr.size = 16; + work->attr.read = test_srm_bin_read; + work->attr.write = test_srm_bin_write; + + ret = sysfs_create_bin_file(kobj, &work->attr); + KUNIT_ASSERT_EQ(test, ret, 0); + + return kobj; +} + +/** + * dm_test_hdcp_destroy_frees_and_removes_sysfs - full teardown path + * @test: KUnit test context + * + * hdcp_destroy() must cancel every link's delayed works, remove the SRM + * sysfs bin file and free srm, srm_temp and the workqueue itself. The + * workqueue and SRM buffers use kzalloc() (not kunit-managed) because + * hdcp_destroy() frees them; KASAN/kmemleak validate there is no leak or + * use-after-free. + */ +static void dm_test_hdcp_destroy_frees_and_removes_sysfs(struct kunit *test) +{ + struct hdcp_workqueue *work; + struct kobject *kobj; + + work = kzalloc_obj(*work); + KUNIT_ASSERT_NOT_NULL(test, work); + + work->max_link = 1; + INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn); + INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn); + + work->srm = kzalloc(16, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work->srm); + work->srm_temp = kzalloc(16, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work->srm_temp); + + kobj = setup_destroy_sysfs(test, work); + + /* Pre-schedule a delayed work to exercise the cancel path. */ + schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000)); + KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork)); + + hdcp_destroy(kobj, work); + + /* work is freed by hdcp_destroy(); only the kobject remains. */ + kobject_put(kobj); +} + +/** + * dm_test_hdcp_destroy_zero_links_null_srm - teardown with no links or SRM + * @test: KUnit test context + * + * With max_link == 0 the cancel loop is skipped, and NULL srm/srm_temp make + * the kfree() calls no-ops. hdcp_destroy() must still remove the sysfs bin + * file and free the workqueue without crashing. + */ +static void dm_test_hdcp_destroy_zero_links_null_srm(struct kunit *test) +{ + struct hdcp_workqueue *work; + struct kobject *kobj; + + work = kzalloc_obj(*work); + KUNIT_ASSERT_NOT_NULL(test, work); + + work->max_link = 0; + work->srm = NULL; + work->srm_temp = NULL; + + kobj = setup_destroy_sysfs(test, work); + + hdcp_destroy(kobj, work); + + kobject_put(kobj); +} + +/* End of tests for hdcp_destroy() */ + /* Tests for link_lock() */ /** @@ -1373,6 +1492,9 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate), /* hdcp_create_workqueue() */ KUNIT_CASE(dm_test_hdcp_create_workqueue_zero_max_links_returns_null), + /* hdcp_destroy() */ + KUNIT_CASE(dm_test_hdcp_destroy_frees_and_removes_sysfs), + KUNIT_CASE(dm_test_hdcp_destroy_zero_links_null_srm), /* link_lock() */ KUNIT_CASE(dm_test_link_lock_locks_and_unlocks_all_links), KUNIT_CASE(dm_test_link_lock_zero_links_is_noop), -- cgit v1.2.3 From 414da24137ace80d8c59fefd43ba3ec9f5f854ba Mon Sep 17 00:00:00 2001 From: Ray Wu Date: Mon, 13 Jul 2026 22:23:34 +0800 Subject: drm/amd/display: Add AV mute wait frames to dce110_set_avmute Port the three-frame wait logic from dcn30_set_avmute to dce110_set_avmute so that older DCN versions (1.0, 2.0) also wait for GCP packets to be sent out before proceeding. This ensures HDMI sinks properly process the mute state, preventing garbled display after link re-establishment. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5167 Reviewed-by: Wayne Lin Signed-off-by: Ray Wu Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/hwss/dce110/dce110_hwseq.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index 262982ca5ad9..f91ce2b4b1bf 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -1336,8 +1336,27 @@ void dce110_blank_stream(struct pipe_ctx *pipe_ctx) void dce110_set_avmute(struct pipe_ctx *pipe_ctx, bool enable) { - if (pipe_ctx != NULL && pipe_ctx->stream_res.stream_enc != NULL) + if (pipe_ctx == NULL || pipe_ctx->stream_res.stream_enc == NULL) + return; + + if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) { pipe_ctx->stream_res.stream_enc->funcs->set_avmute(pipe_ctx->stream_res.stream_enc, enable); + + /* Wait for three frames to make sure AV mute is sent out. + * Some HDMI sinks need additional GCP packets to properly + * process the mute state, especially after link re-establishment + * with HDMI 2.0 scrambling enabled. + */ + if (enable && pipe_ctx->stream_res.tg->funcs->is_tg_enabled(pipe_ctx->stream_res.tg)) { + int i; + + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); + for (i = 0; i < 3; i++) { + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VBLANK); + pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); + } + } + } } enum audio_dto_source translate_to_dto_source(enum controller_id crtc_id) -- cgit v1.2.3 From a648ed2bfafabc321195404a61782811c0f3bf56 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 9 Jul 2026 18:04:43 -0600 Subject: drm/amd/display: move scaling helper to connector [WHAT] amdgpu_dm_update_stream_scaling_settings() computes the stream src/dst rectangles for a connector's scaling mode. It is already declared in amdgpu_dm_connector.h and consumed by create_stream_for_sink(), so move its definition out of the oversized amdgpu_dm.c into amdgpu_dm_connector.c where it belongs. Relocate its KUnit tests from the amdgpu_dm suite to the amdgpu_dm_connector suite accordingly. No functional change. Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 57 ------ .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 57 ++++++ .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 209 +++++++++++++++++++++ .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 208 -------------------- 4 files changed, 266 insertions(+), 265 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 372df3146c19..24e2b7e64595 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3405,63 +3405,6 @@ ffu: &flip_addrs->dirty_rect_count, true); } -void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev, - const struct drm_display_mode *mode, - const struct dm_connector_state *dm_state, - struct dc_stream_state *stream) -{ - enum amdgpu_rmx_type rmx_type; - - struct rect src = { 0 }; /* viewport in composition space*/ - struct rect dst = { 0 }; /* stream addressable area */ - - /* no mode. nothing to be done */ - if (!mode) - return; - - /* Full screen scaling by default */ - src.width = mode->hdisplay; - src.height = mode->vdisplay; - dst.width = stream->timing.h_addressable; - dst.height = stream->timing.v_addressable; - - if (dm_state) { - rmx_type = dm_state->scaling; - if (rmx_type == RMX_ASPECT || rmx_type == RMX_OFF) { - if (src.width * dst.height < - src.height * dst.width) { - /* height needs less upscaling/more downscaling */ - dst.width = src.width * - dst.height / src.height; - } else { - /* width needs less upscaling/more downscaling */ - dst.height = src.height * - dst.width / src.width; - } - } else if (rmx_type == RMX_CENTER) { - dst = src; - } - - dst.x = (stream->timing.h_addressable - dst.width) / 2; - dst.y = (stream->timing.v_addressable - dst.height) / 2; - - if (dm_state->underscan_enable) { - dst.x += dm_state->underscan_hborder / 2; - dst.y += dm_state->underscan_vborder / 2; - dst.width -= dm_state->underscan_hborder; - dst.height -= dm_state->underscan_vborder; - } - } - - stream->src = src; - stream->dst = dst; - - drm_dbg_kms(dev, "Destination Rectangle x:%d y:%d width:%d height:%d\n", - dst.x, dst.y, dst.width, dst.height); - -} -EXPORT_IF_KUNIT(amdgpu_dm_update_stream_scaling_settings); - static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_commit *state, struct dc_state *dc_state, struct dsc_mst_fairness_vars *vars) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index caabf9ed367c..61e992394333 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1389,6 +1389,63 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, } #endif +void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev, + const struct drm_display_mode *mode, + const struct dm_connector_state *dm_state, + struct dc_stream_state *stream) +{ + enum amdgpu_rmx_type rmx_type; + + struct rect src = { 0 }; /* viewport in composition space*/ + struct rect dst = { 0 }; /* stream addressable area */ + + /* no mode. nothing to be done */ + if (!mode) + return; + + /* Full screen scaling by default */ + src.width = mode->hdisplay; + src.height = mode->vdisplay; + dst.width = stream->timing.h_addressable; + dst.height = stream->timing.v_addressable; + + if (dm_state) { + rmx_type = dm_state->scaling; + if (rmx_type == RMX_ASPECT || rmx_type == RMX_OFF) { + if (src.width * dst.height < + src.height * dst.width) { + /* height needs less upscaling/more downscaling */ + dst.width = src.width * + dst.height / src.height; + } else { + /* width needs less upscaling/more downscaling */ + dst.height = src.height * + dst.width / src.width; + } + } else if (rmx_type == RMX_CENTER) { + dst = src; + } + + dst.x = (stream->timing.h_addressable - dst.width) / 2; + dst.y = (stream->timing.v_addressable - dst.height) / 2; + + if (dm_state->underscan_enable) { + dst.x += dm_state->underscan_hborder / 2; + dst.y += dm_state->underscan_vborder / 2; + dst.width -= dm_state->underscan_hborder; + dst.height -= dm_state->underscan_vborder; + } + } + + stream->src = src; + stream->dst = dst; + + drm_dbg_kms(dev, "Destination Rectangle x:%d y:%d width:%d height:%d\n", + dst.x, dst.y, dst.width, dst.height); + +} +EXPORT_IF_KUNIT(amdgpu_dm_update_stream_scaling_settings); + STATIC_IFN_KUNIT struct dc_stream_state * create_stream_for_sink(struct drm_connector *connector, const struct drm_display_mode *drm_mode, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 2c7370f586a0..1a8f02c78c4d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -28,6 +28,7 @@ #include "amdgpu_dm_connector.h" #include "amdgpu_dm_backlight.h" #include "include/grph_object_id.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* Tests for get_subconnector_type() */ @@ -5061,6 +5062,206 @@ static void dm_test_update_after_detect_sink_unchanged(struct kunit *test) KUNIT_EXPECT_NULL(test, aconnector->dc_sink); } +/* Tests for amdgpu_dm_update_stream_scaling_settings() */ + +/** + * dm_test_update_scaling_null_mode - Test NULL mode leaves the stream rects untouched + * @test: The KUnit test context + */ +static void dm_test_update_scaling_null_mode(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, NULL, NULL, stream); + + /* NULL mode: early return before touching src/dst */ + KUNIT_EXPECT_EQ(test, stream->src.width, 0); + KUNIT_EXPECT_EQ(test, stream->dst.width, 0); +} + +/** + * dm_test_update_scaling_fullscreen_default - Test full-screen default with no dm_state + * @test: The KUnit test context + */ +static void dm_test_update_scaling_fullscreen_default(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct drm_display_mode mode = { 0 }; + + mode.hdisplay = 1920; + mode.vdisplay = 1080; + stream->timing.h_addressable = 2560; + stream->timing.v_addressable = 1440; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, NULL, stream); + + /* src = mode, dst = timing addressable, no centering without dm_state */ + KUNIT_EXPECT_EQ(test, stream->src.width, 1920); + KUNIT_EXPECT_EQ(test, stream->src.height, 1080); + KUNIT_EXPECT_EQ(test, stream->dst.width, 2560); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1440); + KUNIT_EXPECT_EQ(test, stream->dst.x, 0); + KUNIT_EXPECT_EQ(test, stream->dst.y, 0); +} + +/** + * dm_test_update_scaling_rmx_full - Test RMX_FULL keeps a full-size, centered dst + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_full(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + mode.hdisplay = 1280; + mode.vdisplay = 720; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_FULL; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* RMX_FULL: dst stays full addressable, offset 0 */ + KUNIT_EXPECT_EQ(test, stream->dst.width, 1920); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1080); + KUNIT_EXPECT_EQ(test, stream->dst.x, 0); + KUNIT_EXPECT_EQ(test, stream->dst.y, 0); +} + +/** + * dm_test_update_scaling_rmx_aspect_pillarbox - Test RMX_ASPECT preserves aspect ratio + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_aspect_pillarbox(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + /* 4:3 source on a 16:9 panel -> pillarboxed */ + mode.hdisplay = 1024; + mode.vdisplay = 768; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_ASPECT; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* + * src.width*dst.height (1024*1080) < src.height*dst.width (768*1920): + * width scaled to src.width*dst.height/src.height = 1440, height stays + * 1080, centered horizontally at (1920-1440)/2 = 240. + */ + KUNIT_EXPECT_EQ(test, stream->dst.width, 1440); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1080); + KUNIT_EXPECT_EQ(test, stream->dst.x, 240); + KUNIT_EXPECT_EQ(test, stream->dst.y, 0); +} + +/** + * dm_test_update_scaling_rmx_aspect_letterbox - Test RMX_ASPECT letterboxes wide sources + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_aspect_letterbox(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + /* 16:9 source on a 4:3 panel -> letterboxed */ + mode.hdisplay = 1920; + mode.vdisplay = 1080; + stream->timing.h_addressable = 1024; + stream->timing.v_addressable = 768; + dm_state->scaling = RMX_ASPECT; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + KUNIT_EXPECT_EQ(test, stream->dst.width, 1024); + KUNIT_EXPECT_EQ(test, stream->dst.height, 576); + KUNIT_EXPECT_EQ(test, stream->dst.x, 0); + KUNIT_EXPECT_EQ(test, stream->dst.y, 96); +} + +/** + * dm_test_update_scaling_rmx_center - Test RMX_CENTER centers a 1:1 dst + * @test: The KUnit test context + */ +static void dm_test_update_scaling_rmx_center(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + mode.hdisplay = 1280; + mode.vdisplay = 720; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_CENTER; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* RMX_CENTER: dst = src, centered on the addressable area */ + KUNIT_EXPECT_EQ(test, stream->dst.width, 1280); + KUNIT_EXPECT_EQ(test, stream->dst.height, 720); + KUNIT_EXPECT_EQ(test, stream->dst.x, 320); + KUNIT_EXPECT_EQ(test, stream->dst.y, 180); +} + +/** + * dm_test_update_scaling_underscan - Test underscan borders shrink and offset dst + * @test: The KUnit test context + */ +static void dm_test_update_scaling_underscan(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); + struct dm_connector_state *dm_state; + struct drm_display_mode mode = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + mode.hdisplay = 1920; + mode.vdisplay = 1080; + stream->timing.h_addressable = 1920; + stream->timing.v_addressable = 1080; + dm_state->scaling = RMX_FULL; + dm_state->underscan_enable = true; + dm_state->underscan_hborder = 64; + dm_state->underscan_vborder = 32; + + amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); + + /* Full dst, then underscan: x/y += border/2, width/height -= border */ + KUNIT_EXPECT_EQ(test, stream->dst.x, 32); + KUNIT_EXPECT_EQ(test, stream->dst.y, 16); + KUNIT_EXPECT_EQ(test, stream->dst.width, 1856); + KUNIT_EXPECT_EQ(test, stream->dst.height, 1048); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -5340,6 +5541,14 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { /* amdgpu_dm_update_connector_after_detect */ KUNIT_CASE(dm_test_update_after_detect_mst_noop), KUNIT_CASE(dm_test_update_after_detect_sink_unchanged), + /* amdgpu_dm_update_stream_scaling_settings */ + KUNIT_CASE(dm_test_update_scaling_null_mode), + KUNIT_CASE(dm_test_update_scaling_fullscreen_default), + KUNIT_CASE(dm_test_update_scaling_rmx_full), + KUNIT_CASE(dm_test_update_scaling_rmx_aspect_pillarbox), + KUNIT_CASE(dm_test_update_scaling_rmx_aspect_letterbox), + KUNIT_CASE(dm_test_update_scaling_rmx_center), + KUNIT_CASE(dm_test_update_scaling_underscan), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index f967eced6dbb..88ac5e4f6c04 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -1225,206 +1225,6 @@ static void dm_test_master_stream_defaults_to_first(struct kunit *test) stream0); } -/* Tests for amdgpu_dm_update_stream_scaling_settings() */ - -/** - * dm_test_update_scaling_null_mode - Test NULL mode leaves the stream rects untouched - * @test: The KUnit test context - */ -static void dm_test_update_scaling_null_mode(struct kunit *test) -{ - struct amdgpu_device *adev = dm_kunit_alloc_adev(test); - struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); - - stream->timing.h_addressable = 1920; - stream->timing.v_addressable = 1080; - - amdgpu_dm_update_stream_scaling_settings(&adev->ddev, NULL, NULL, stream); - - /* NULL mode: early return before touching src/dst */ - KUNIT_EXPECT_EQ(test, stream->src.width, 0); - KUNIT_EXPECT_EQ(test, stream->dst.width, 0); -} - -/** - * dm_test_update_scaling_fullscreen_default - Test full-screen default with no dm_state - * @test: The KUnit test context - */ -static void dm_test_update_scaling_fullscreen_default(struct kunit *test) -{ - struct amdgpu_device *adev = dm_kunit_alloc_adev(test); - struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); - struct drm_display_mode mode = { 0 }; - - mode.hdisplay = 1920; - mode.vdisplay = 1080; - stream->timing.h_addressable = 2560; - stream->timing.v_addressable = 1440; - - amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, NULL, stream); - - /* src = mode, dst = timing addressable, no centering without dm_state */ - KUNIT_EXPECT_EQ(test, stream->src.width, 1920); - KUNIT_EXPECT_EQ(test, stream->src.height, 1080); - KUNIT_EXPECT_EQ(test, stream->dst.width, 2560); - KUNIT_EXPECT_EQ(test, stream->dst.height, 1440); - KUNIT_EXPECT_EQ(test, stream->dst.x, 0); - KUNIT_EXPECT_EQ(test, stream->dst.y, 0); -} - -/** - * dm_test_update_scaling_rmx_full - Test RMX_FULL keeps a full-size, centered dst - * @test: The KUnit test context - */ -static void dm_test_update_scaling_rmx_full(struct kunit *test) -{ - struct amdgpu_device *adev = dm_kunit_alloc_adev(test); - struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); - struct dm_connector_state *dm_state; - struct drm_display_mode mode = { 0 }; - - dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm_state); - - mode.hdisplay = 1280; - mode.vdisplay = 720; - stream->timing.h_addressable = 1920; - stream->timing.v_addressable = 1080; - dm_state->scaling = RMX_FULL; - - amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); - - /* RMX_FULL: dst stays full addressable, offset 0 */ - KUNIT_EXPECT_EQ(test, stream->dst.width, 1920); - KUNIT_EXPECT_EQ(test, stream->dst.height, 1080); - KUNIT_EXPECT_EQ(test, stream->dst.x, 0); - KUNIT_EXPECT_EQ(test, stream->dst.y, 0); -} - -/** - * dm_test_update_scaling_rmx_aspect_pillarbox - Test RMX_ASPECT preserves aspect ratio - * @test: The KUnit test context - */ -static void dm_test_update_scaling_rmx_aspect_pillarbox(struct kunit *test) -{ - struct amdgpu_device *adev = dm_kunit_alloc_adev(test); - struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); - struct dm_connector_state *dm_state; - struct drm_display_mode mode = { 0 }; - - dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm_state); - - /* 4:3 source on a 16:9 panel -> pillarboxed */ - mode.hdisplay = 1024; - mode.vdisplay = 768; - stream->timing.h_addressable = 1920; - stream->timing.v_addressable = 1080; - dm_state->scaling = RMX_ASPECT; - - amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); - - /* - * src.width*dst.height (1024*1080) < src.height*dst.width (768*1920): - * width scaled to src.width*dst.height/src.height = 1440, height stays - * 1080, centered horizontally at (1920-1440)/2 = 240. - */ - KUNIT_EXPECT_EQ(test, stream->dst.width, 1440); - KUNIT_EXPECT_EQ(test, stream->dst.height, 1080); - KUNIT_EXPECT_EQ(test, stream->dst.x, 240); - KUNIT_EXPECT_EQ(test, stream->dst.y, 0); -} - -/** - * dm_test_update_scaling_rmx_aspect_letterbox - Test RMX_ASPECT letterboxes wide sources - * @test: The KUnit test context - */ -static void dm_test_update_scaling_rmx_aspect_letterbox(struct kunit *test) -{ - struct amdgpu_device *adev = dm_kunit_alloc_adev(test); - struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); - struct dm_connector_state *dm_state; - struct drm_display_mode mode = { 0 }; - - dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm_state); - - /* 16:9 source on a 4:3 panel -> letterboxed */ - mode.hdisplay = 1920; - mode.vdisplay = 1080; - stream->timing.h_addressable = 1024; - stream->timing.v_addressable = 768; - dm_state->scaling = RMX_ASPECT; - - amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); - - KUNIT_EXPECT_EQ(test, stream->dst.width, 1024); - KUNIT_EXPECT_EQ(test, stream->dst.height, 576); - KUNIT_EXPECT_EQ(test, stream->dst.x, 0); - KUNIT_EXPECT_EQ(test, stream->dst.y, 96); -} - -/** - * dm_test_update_scaling_rmx_center - Test RMX_CENTER centers a 1:1 dst - * @test: The KUnit test context - */ -static void dm_test_update_scaling_rmx_center(struct kunit *test) -{ - struct amdgpu_device *adev = dm_kunit_alloc_adev(test); - struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); - struct dm_connector_state *dm_state; - struct drm_display_mode mode = { 0 }; - - dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm_state); - - mode.hdisplay = 1280; - mode.vdisplay = 720; - stream->timing.h_addressable = 1920; - stream->timing.v_addressable = 1080; - dm_state->scaling = RMX_CENTER; - - amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); - - /* RMX_CENTER: dst = src, centered on the addressable area */ - KUNIT_EXPECT_EQ(test, stream->dst.width, 1280); - KUNIT_EXPECT_EQ(test, stream->dst.height, 720); - KUNIT_EXPECT_EQ(test, stream->dst.x, 320); - KUNIT_EXPECT_EQ(test, stream->dst.y, 180); -} - -/** - * dm_test_update_scaling_underscan - Test underscan borders shrink and offset dst - * @test: The KUnit test context - */ -static void dm_test_update_scaling_underscan(struct kunit *test) -{ - struct amdgpu_device *adev = dm_kunit_alloc_adev(test); - struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); - struct dm_connector_state *dm_state; - struct drm_display_mode mode = { 0 }; - - dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm_state); - - mode.hdisplay = 1920; - mode.vdisplay = 1080; - stream->timing.h_addressable = 1920; - stream->timing.v_addressable = 1080; - dm_state->scaling = RMX_FULL; - dm_state->underscan_enable = true; - dm_state->underscan_hborder = 64; - dm_state->underscan_vborder = 32; - - amdgpu_dm_update_stream_scaling_settings(&adev->ddev, &mode, dm_state, stream); - - /* Full dst, then underscan: x/y += border/2, width/height -= border */ - KUNIT_EXPECT_EQ(test, stream->dst.x, 32); - KUNIT_EXPECT_EQ(test, stream->dst.y, 16); - KUNIT_EXPECT_EQ(test, stream->dst.width, 1856); - KUNIT_EXPECT_EQ(test, stream->dst.height, 1048); -} - /* Tests for is_content_protection_different() */ struct dm_test_cp_ctx { @@ -2113,14 +1913,6 @@ static struct kunit_case amdgpu_dm_tests[] = { /* set_master_stream */ KUNIT_CASE(dm_test_master_stream_highest_refresh), KUNIT_CASE(dm_test_master_stream_defaults_to_first), - /* amdgpu_dm_update_stream_scaling_settings */ - KUNIT_CASE(dm_test_update_scaling_null_mode), - KUNIT_CASE(dm_test_update_scaling_fullscreen_default), - KUNIT_CASE(dm_test_update_scaling_rmx_full), - KUNIT_CASE(dm_test_update_scaling_rmx_aspect_pillarbox), - KUNIT_CASE(dm_test_update_scaling_rmx_aspect_letterbox), - KUNIT_CASE(dm_test_update_scaling_rmx_center), - KUNIT_CASE(dm_test_update_scaling_underscan), /* is_content_protection_different */ KUNIT_CASE(dm_test_cp_diff_hdcp_type_change), KUNIT_CASE(dm_test_cp_diff_reenable_mode_changed), -- cgit v1.2.3 From e278e0bf3ed30f2eec5e3d28459714e2c1e96cb1 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 9 Jul 2026 18:08:30 -0600 Subject: drm/amd/display: move stutter quirk to quirks file [WHAT] dm_should_disable_stutter() and its amdgpu_stutter_quirk PCI match table are self-contained quirk logic. Move them out of amdgpu_dm.c into amdgpu_dm_quirks.c alongside the existing DMI quirk handling. The helper is called from amdgpu_dm_init(), so it becomes a plain exported function instead of STATIC_IFN_KUNIT, and its declaration moves from the KUnit-only block to a regular prototype in amdgpu_dm.h. Relocate its KUnit tests from the amdgpu_dm suite to the amdgpu_dm_quirks suite. No functional change. Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 33 ----------- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 4 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c | 32 +++++++++++ .../amdgpu_dm/tests/amdgpu_dm_quirks_test.c | 65 ++++++++++++++++++++++ .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 64 --------------------- 5 files changed, 99 insertions(+), 99 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 24e2b7e64595..3cb477eda631 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -410,39 +410,6 @@ static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_ } -struct amdgpu_stutter_quirk { - u16 chip_vendor; - u16 chip_device; - u16 subsys_vendor; - u16 subsys_device; - u8 revision; -}; - -static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = { - /* https://bugzilla.kernel.org/show_bug.cgi?id=214417 */ - { 0x1002, 0x15dd, 0x1002, 0x15dd, 0xc8 }, - { 0, 0, 0, 0, 0 }, -}; - -STATIC_IFN_KUNIT bool dm_should_disable_stutter(struct pci_dev *pdev) -{ - const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list; - - while (p && p->chip_device != 0) { - if (pdev->vendor == p->chip_vendor && - pdev->device == p->chip_device && - pdev->subsystem_vendor == p->subsys_vendor && - pdev->subsystem_device == p->subsys_device && - pdev->revision == p->revision) { - return true; - } - ++p; - } - return false; -} -EXPORT_IF_KUNIT(dm_should_disable_stutter); - - void* dm_allocate_gpu_mem( struct amdgpu_device *adev, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 03189f44efd4..617328497203 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1134,6 +1134,8 @@ bool amdgpu_dm_is_headless(struct amdgpu_device *adev); bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc); void retrieve_dmi_info(struct amdgpu_display_manager *dm); +struct pci_dev; +bool dm_should_disable_stutter(struct pci_dev *pdev); void amdgpu_dm_emulated_link_detect(struct dc_link *link); void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, @@ -1178,8 +1180,6 @@ bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, struct dm_crtc_state *new_state); void set_multisync_trigger_params(struct dc_stream_state *stream); void set_master_stream(struct dc_stream_state *stream_set[], int stream_count); -struct pci_dev; -bool dm_should_disable_stutter(struct pci_dev *pdev); void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); void get_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state, struct dm_connector_state *new_con_state); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c index 0a7602ed70b2..259966703cef 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c @@ -178,3 +178,35 @@ void retrieve_dmi_info(struct amdgpu_display_manager *dm) } } EXPORT_IF_KUNIT(retrieve_dmi_info); + +struct amdgpu_stutter_quirk { + u16 chip_vendor; + u16 chip_device; + u16 subsys_vendor; + u16 subsys_device; + u8 revision; +}; + +static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = { + /* https://bugzilla.kernel.org/show_bug.cgi?id=214417 */ + { 0x1002, 0x15dd, 0x1002, 0x15dd, 0xc8 }, + { 0, 0, 0, 0, 0 }, +}; + +bool dm_should_disable_stutter(struct pci_dev *pdev) +{ + const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list; + + while (p && p->chip_device != 0) { + if (pdev->vendor == p->chip_vendor && + pdev->device == p->chip_device && + pdev->subsystem_vendor == p->subsys_vendor && + pdev->subsystem_device == p->subsys_device && + pdev->revision == p->revision) { + return true; + } + ++p; + } + return false; +} +EXPORT_IF_KUNIT(dm_should_disable_stutter); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c index a09f31ee0a2a..150c53a68bed 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c @@ -6,6 +6,7 @@ */ #include +#include #include "dc.h" #include "amdgpu_mode.h" @@ -83,11 +84,75 @@ static void dm_test_quirks_no_dmi_match_both_false(struct kunit *test) KUNIT_EXPECT_FALSE(test, dm->edp0_on_dp1_quirk); } +/* Tests for dm_should_disable_stutter() */ + +/** + * dm_test_should_disable_stutter_match - Test the quirk device matches + * @test: The KUnit test context + */ +static void dm_test_should_disable_stutter_match(struct kunit *test) +{ + struct pci_dev *pdev; + + pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pdev); + + pdev->vendor = 0x1002; + pdev->device = 0x15dd; + pdev->subsystem_vendor = 0x1002; + pdev->subsystem_device = 0x15dd; + pdev->revision = 0xc8; + + KUNIT_EXPECT_TRUE(test, dm_should_disable_stutter(pdev)); +} + +/** + * dm_test_should_disable_stutter_no_match - Test a non-quirk device does not match + * @test: The KUnit test context + */ +static void dm_test_should_disable_stutter_no_match(struct kunit *test) +{ + struct pci_dev *pdev; + + pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pdev); + + pdev->vendor = 0x1002; + pdev->device = 0x1234; + + KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev)); +} + +/** + * dm_test_should_disable_stutter_revision_differs - Test a partial match (revision) fails + * @test: The KUnit test context + */ +static void dm_test_should_disable_stutter_revision_differs(struct kunit *test) +{ + struct pci_dev *pdev; + + pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pdev); + + /* Everything matches the quirk except the revision */ + pdev->vendor = 0x1002; + pdev->device = 0x15dd; + pdev->subsystem_vendor = 0x1002; + pdev->subsystem_device = 0x15dd; + pdev->revision = 0x00; + + KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev)); +} + static struct kunit_case amdgpu_dm_quirks_tests[] = { /* retrieve_dmi_info */ KUNIT_CASE(dm_test_quirks_aux_hpd_discon_reset), KUNIT_CASE(dm_test_quirks_edp0_on_dp1_reset), KUNIT_CASE(dm_test_quirks_no_dmi_match_both_false), + /* dm_should_disable_stutter */ + KUNIT_CASE(dm_test_should_disable_stutter_match), + KUNIT_CASE(dm_test_should_disable_stutter_no_match), + KUNIT_CASE(dm_test_should_disable_stutter_revision_differs), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 88ac5e4f6c04..72d5a4012a3c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -1737,66 +1737,6 @@ static void dm_test_per_frame_master_sync_skips_null_stream(struct kunit *test) stream); } -/* Tests for dm_should_disable_stutter() */ - -/** - * dm_test_should_disable_stutter_match - Test the quirk device matches - * @test: The KUnit test context - */ -static void dm_test_should_disable_stutter_match(struct kunit *test) -{ - struct pci_dev *pdev; - - pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, pdev); - - pdev->vendor = 0x1002; - pdev->device = 0x15dd; - pdev->subsystem_vendor = 0x1002; - pdev->subsystem_device = 0x15dd; - pdev->revision = 0xc8; - - KUNIT_EXPECT_TRUE(test, dm_should_disable_stutter(pdev)); -} - -/** - * dm_test_should_disable_stutter_no_match - Test a non-quirk device does not match - * @test: The KUnit test context - */ -static void dm_test_should_disable_stutter_no_match(struct kunit *test) -{ - struct pci_dev *pdev; - - pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, pdev); - - pdev->vendor = 0x1002; - pdev->device = 0x1234; - - KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev)); -} - -/** - * dm_test_should_disable_stutter_revision_differs - Test a partial match (revision) fails - * @test: The KUnit test context - */ -static void dm_test_should_disable_stutter_revision_differs(struct kunit *test) -{ - struct pci_dev *pdev; - - pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, pdev); - - /* Everything matches the quirk except the revision */ - pdev->vendor = 0x1002; - pdev->device = 0x15dd; - pdev->subsystem_vendor = 0x1002; - pdev->subsystem_device = 0x15dd; - pdev->revision = 0x00; - - KUNIT_EXPECT_FALSE(test, dm_should_disable_stutter(pdev)); -} - /* Tests for amdgpu_dm_apply_delay_after_dpcd_poweroff() */ /** @@ -1938,10 +1878,6 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_per_frame_master_sync_single_stream), KUNIT_CASE(dm_test_per_frame_master_sync_two_streams), KUNIT_CASE(dm_test_per_frame_master_sync_skips_null_stream), - /* dm_should_disable_stutter */ - KUNIT_CASE(dm_test_should_disable_stutter_match), - KUNIT_CASE(dm_test_should_disable_stutter_no_match), - KUNIT_CASE(dm_test_should_disable_stutter_revision_differs), /* amdgpu_dm_apply_delay_after_dpcd_poweroff */ KUNIT_CASE(dm_test_apply_delay_null_sink), KUNIT_CASE(dm_test_apply_delay_zero_wait), -- cgit v1.2.3 From 0b9bd593f0a2c02c505a803ebf5e7bc27515312e Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 9 Jul 2026 18:12:33 -0600 Subject: drm/amd/display: move watermarks table to pp_smu [WHAT] amdgpu_dm_smu_write_watermarks_table() pushes fixed dcn watermark clock settings to the SMU on Navi1x. It belongs with the other dc-pplib bridge code, so move it out of amdgpu_dm.c into amdgpu_dm_pp_smu.c. It is called from dm_resume(), so it becomes a plain exported function declared in amdgpu_dm_pp_smu.h (now included by amdgpu_dm.c) instead of STATIC_IFN_KUNIT in amdgpu_dm.h. Relocate its KUnit test from the amdgpu_dm suite to the amdgpu_dm_pp_smu suite. No functional change. Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 53 +--------------------- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 1 - .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 52 +++++++++++++++++++++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 3 ++ .../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 20 ++++++++ .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 16 ------- 6 files changed, 76 insertions(+), 69 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 3cb477eda631..f017d6956a19 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -70,6 +70,7 @@ #include "amdgpu_dm_audio.h" #include "amdgpu_dm_dmub.h" #include "amdgpu_dm_connector.h" +#include "amdgpu_dm_pp_smu.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -1372,58 +1373,6 @@ static void s3_handle_mst(struct drm_device *dev, bool suspend) drm_connector_list_iter_end(&iter); } -STATIC_IFN_KUNIT int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev) -{ - int ret = 0; - - /* This interface is for dGPU Navi1x.Linux dc-pplib interface depends - * on window driver dc implementation. - * For Navi1x, clock settings of dcn watermarks are fixed. the settings - * should be passed to smu during boot up and resume from s3. - * boot up: dc calculate dcn watermark clock settings within dc_create, - * dcn20_resource_construct - * then call pplib functions below to pass the settings to smu: - * smu_set_watermarks_for_clock_ranges - * smu_set_watermarks_table - * navi10_set_watermarks_table - * smu_write_watermarks_table - * - * For Renoir, clock settings of dcn watermark are also fixed values. - * dc has implemented different flow for window driver: - * dc_hardware_init / dc_set_power_state - * dcn10_init_hw - * notify_wm_ranges - * set_wm_ranges - * -- Linux - * smu_set_watermarks_for_clock_ranges - * renoir_set_watermarks_table - * smu_write_watermarks_table - * - * For Linux, - * dc_hardware_init -> amdgpu_dm_init - * dc_set_power_state --> dm_resume - * - * therefore, this function apply to navi10/12/14 but not Renoir - * * - */ - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(2, 0, 2): - case IP_VERSION(2, 0, 0): - break; - default: - return 0; - } - - ret = amdgpu_dpm_write_watermarks_table(adev); - if (ret) { - drm_err(adev_to_drm(adev), "Failed to update WMTABLE!\n"); - return ret; - } - - return 0; -} -EXPORT_IF_KUNIT(amdgpu_dm_smu_write_watermarks_table); - static int dm_oem_i2c_hw_init(struct amdgpu_device *adev) { struct amdgpu_display_manager *dm = &adev->dm; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 617328497203..70220997cde9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1157,7 +1157,6 @@ int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, struct dm_atomic_state *dm_atomic_get_new_state(struct drm_atomic_commit *state); void dm_atomic_destroy_state(struct drm_private_obj *obj, struct drm_private_state *state); -int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev); bool dm_should_update_native_cursor(struct drm_atomic_commit *state, struct drm_crtc *old_plane_crtc, struct drm_crtc *new_plane_crtc, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index 8431e164a0db..3280bfadc6a8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -792,3 +792,55 @@ void dm_pp_get_funcs( } } EXPORT_IF_KUNIT(dm_pp_get_funcs); + +int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev) +{ + int ret = 0; + + /* This interface is for dGPU Navi1x.Linux dc-pplib interface depends + * on window driver dc implementation. + * For Navi1x, clock settings of dcn watermarks are fixed. the settings + * should be passed to smu during boot up and resume from s3. + * boot up: dc calculate dcn watermark clock settings within dc_create, + * dcn20_resource_construct + * then call pplib functions below to pass the settings to smu: + * smu_set_watermarks_for_clock_ranges + * smu_set_watermarks_table + * navi10_set_watermarks_table + * smu_write_watermarks_table + * + * For Renoir, clock settings of dcn watermark are also fixed values. + * dc has implemented different flow for window driver: + * dc_hardware_init / dc_set_power_state + * dcn10_init_hw + * notify_wm_ranges + * set_wm_ranges + * -- Linux + * smu_set_watermarks_for_clock_ranges + * renoir_set_watermarks_table + * smu_write_watermarks_table + * + * For Linux, + * dc_hardware_init -> amdgpu_dm_init + * dc_set_power_state --> dm_resume + * + * therefore, this function apply to navi10/12/14 but not Renoir + * * + */ + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(2, 0, 2): + case IP_VERSION(2, 0, 0): + break; + default: + return 0; + } + + ret = amdgpu_dpm_write_watermarks_table(adev); + if (ret) { + drm_err(adev_to_drm(adev), "Failed to update WMTABLE!\n"); + return ret; + } + + return 0; +} +EXPORT_IF_KUNIT(amdgpu_dm_smu_write_watermarks_table); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h index f918eb71f0d1..1d153d0e2f03 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h @@ -11,6 +11,9 @@ struct amd_pp_display_configuration; struct pp_smu_wm_range_sets; struct dm_pp_wm_sets_with_clock_ranges_soc15; +struct amdgpu_device; + +int amdgpu_dm_smu_write_watermarks_table(struct amdgpu_device *adev); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) void build_pm_display_cfg(struct amd_pp_display_configuration *pm_display_cfg, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c index 8d1d26bfcc16..fbd07af60230 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c @@ -16,6 +16,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_pp_smu.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* ---- Stub DPM layer ---- */ @@ -2321,6 +2322,23 @@ static void dm_test_nv_get_uclk_dpm_states_fail(struct kunit *test) (int)PP_SMU_RESULT_FAIL); } +/* Tests for amdgpu_dm_smu_write_watermarks_table() */ + +/** + * dm_test_smu_write_watermarks_table_default - Test watermarks table skips non-Navi1x IP versions + * @test: The KUnit test context + */ +static void dm_test_smu_write_watermarks_table_default(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + + /* + * A zeroed adev reports DCE IP version 0, which is not one of the + * Navi1x versions handled by the switch, so the function returns early. + */ + KUNIT_EXPECT_EQ(test, amdgpu_dm_smu_write_watermarks_table(adev), 0); +} + static struct kunit_case dm_pp_smu_test_cases[] = { /* get_default_clock_levels */ KUNIT_CASE(dm_test_default_clock_levels_display), @@ -2440,6 +2458,8 @@ static struct kunit_case dm_pp_smu_test_cases[] = { KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_ok), KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_unsupported), KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_fail), + /* amdgpu_dm_smu_write_watermarks_table */ + KUNIT_CASE(dm_test_smu_write_watermarks_table_default), {} }; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 72d5a4012a3c..49a1d2ae5588 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -316,21 +316,6 @@ static void dm_test_atomic_destroy_state_no_context(struct kunit *test) dm_atomic_destroy_state(NULL, &dm_state->base); } -/** - * dm_test_smu_write_watermarks_table_default - Test watermarks table skips non-Navi1x IP versions - * @test: The KUnit test context - */ -static void dm_test_smu_write_watermarks_table_default(struct kunit *test) -{ - struct amdgpu_device *adev = dm_kunit_alloc_adev(test); - - /* - * A zeroed adev reports DCE IP version 0, which is not one of the - * Navi1x versions handled by the switch, so the function returns early. - */ - KUNIT_EXPECT_EQ(test, amdgpu_dm_smu_write_watermarks_table(adev), 0); -} - /* Tests for dm_plane_layer_index_cmp() */ /** @@ -1801,7 +1786,6 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_should_update_native_cursor_disable_native), KUNIT_CASE(dm_test_should_update_native_cursor_enable_overlay), KUNIT_CASE(dm_test_atomic_destroy_state_no_context), - KUNIT_CASE(dm_test_smu_write_watermarks_table_default), /* dm_plane_layer_index_cmp */ KUNIT_CASE(dm_test_plane_layer_index_cmp_equal), KUNIT_CASE(dm_test_plane_layer_index_cmp_descending), -- cgit v1.2.3 From 66517ca0cd8bb6e6a649c577ca67c2ebe8c27c97 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 9 Jul 2026 18:14:57 -0600 Subject: drm/amd/display: move GPU mem helpers to services [WHAT] dm_allocate_gpu_mem() and dm_free_gpu_mem() are generic DM service helpers that wrap amdgpu_bo_create_kernel()/amdgpu_bo_free_kernel() and track allocations on adev->dm.da_list. Move them out of amdgpu_dm.c into amdgpu_dm_services.c alongside the other dm_* service callbacks. Both are already public and declared in amdgpu_dm.h; their callers in amdgpu_dm_dmub.c and amdgpu_dm_helpers.c are unaffected. No functional change. Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 53 ---------------------- .../drm/amd/display/amdgpu_dm/amdgpu_dm_services.c | 53 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index f017d6956a19..e48db5b6ebf5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -411,59 +411,6 @@ static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_ } -void* -dm_allocate_gpu_mem( - struct amdgpu_device *adev, - enum dc_gpu_mem_alloc_type type, - size_t size, - long long *addr) -{ - struct dal_allocation *da; - u32 domain = (type == DC_MEM_ALLOC_TYPE_GART) ? - AMDGPU_GEM_DOMAIN_GTT : AMDGPU_GEM_DOMAIN_VRAM; - int ret; - - da = kzalloc_obj(struct dal_allocation); - if (!da) - return NULL; - - ret = amdgpu_bo_create_kernel(adev, size, PAGE_SIZE, - domain, &da->bo, - &da->gpu_addr, &da->cpu_ptr); - - *addr = da->gpu_addr; - - if (ret) { - kfree(da); - return NULL; - } - - /* add da to list in dm */ - list_add(&da->list, &adev->dm.da_list); - - return da->cpu_ptr; -} - -void -dm_free_gpu_mem( - struct amdgpu_device *adev, - enum dc_gpu_mem_alloc_type type, - void *pvMem) -{ - struct dal_allocation *da; - - /* walk the da list in DM */ - list_for_each_entry(da, &adev->dm.da_list, list) { - if (pvMem == da->cpu_ptr) { - amdgpu_bo_free_kernel(&da->bo, &da->gpu_addr, &da->cpu_ptr); - list_del(&da->list); - kfree(da); - break; - } - } - -} - static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) { struct mod_power_init_params init_data[MAX_NUM_EDP]; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c index dbdda6ed12d4..6dbc8d1b4ecd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c @@ -96,3 +96,56 @@ bool dm_query_extended_brightness_caps(struct dc_context *ctx, return true; } EXPORT_IF_KUNIT(dm_query_extended_brightness_caps); + +void* +dm_allocate_gpu_mem( + struct amdgpu_device *adev, + enum dc_gpu_mem_alloc_type type, + size_t size, + long long *addr) +{ + struct dal_allocation *da; + u32 domain = (type == DC_MEM_ALLOC_TYPE_GART) ? + AMDGPU_GEM_DOMAIN_GTT : AMDGPU_GEM_DOMAIN_VRAM; + int ret; + + da = kzalloc_obj(*da); + if (!da) + return NULL; + + ret = amdgpu_bo_create_kernel(adev, size, PAGE_SIZE, + domain, &da->bo, + &da->gpu_addr, &da->cpu_ptr); + + *addr = da->gpu_addr; + + if (ret) { + kfree(da); + return NULL; + } + + /* add da to list in dm */ + list_add(&da->list, &adev->dm.da_list); + + return da->cpu_ptr; +} + +void +dm_free_gpu_mem( + struct amdgpu_device *adev, + enum dc_gpu_mem_alloc_type type, + void *pvMem) +{ + struct dal_allocation *da; + + /* walk the da list in DM */ + list_for_each_entry(da, &adev->dm.da_list, list) { + if (pvMem == da->cpu_ptr) { + amdgpu_bo_free_kernel(&da->bo, &da->gpu_addr, &da->cpu_ptr); + list_del(&da->list); + kfree(da); + break; + } + } + +} -- cgit v1.2.3 From 09dd9fbc0b2dcd611f8ed5937eb47d1fe7788406 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 9 Jul 2026 19:56:03 -0600 Subject: drm/amd/display: add FreeSync/VRR module [WHAT] Group the FreeSync/VRR helpers from amdgpu_dm.c into a dedicated amdgpu_dm_freesync.c and amdgpu_dm_freesync.h. The moved functions are: is_dc_timing_adjust_needed, is_timing_unchanged_for_freesync, set_freesync_fixed_config, reset_freesync_config_for_crtc, get_freesync_config_for_crtc, update_freesync_state_on_stream, update_stream_irq_parameters, amdgpu_dm_handle_vrr_transition. The FreeSync/VRR helpers exposed by amdgpu_dm_freesync.h are called from amdgpu_dm.c, so give the seven that lacked a namespace prefix the standard amdgpu_dm_ prefix: Relocate the corresponding KUnit tests into amdgpu_dm_freesync_test.c. No functional change. Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 349 +--------------- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 8 - .../drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c | 362 ++++++++++++++++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h | 65 +++ .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_freesync_test.c | 453 +++++++++++++++++++++ .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 417 ------------------- 8 files changed, 894 insertions(+), 764 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index d83878e35b61..5cb8c143e72f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -45,7 +45,8 @@ AMDGPUDM = \ amdgpu_dm_backlight.o \ amdgpu_dm_audio.o \ amdgpu_dm_dmub.o \ - amdgpu_dm_connector.o + amdgpu_dm_connector.o \ + amdgpu_dm_freesync.o ifdef CONFIG_DRM_AMD_DC_FP AMDGPUDM += dc_fpu.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index e48db5b6ebf5..09281e003a43 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -71,6 +71,7 @@ #include "amdgpu_dm_dmub.h" #include "amdgpu_dm_connector.h" #include "amdgpu_dm_pp_smu.h" +#include "amdgpu_dm_freesync.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -127,7 +128,6 @@ MODULE_FIRMWARE(FIRMWARE_NAVI12_DMCU); /* basic init/fini API */ static int amdgpu_dm_init(struct amdgpu_device *adev); static void amdgpu_dm_fini(struct amdgpu_device *adev); -STATIC_IFN_KUNIT void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); /* * initializes drm_device display related structures, based on the information @@ -147,10 +147,6 @@ STATIC_IFN_KUNIT void dm_enable_per_frame_crtc_master_sync(struct dc_state *cont static int amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); -STATIC_IFN_KUNIT bool -is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, - struct drm_crtc_state *new_crtc_state); - static inline void amdgpu_dm_exit_ips_for_hw_access(struct dc *dc) { if (dc->ctx->dmub_srv && !dc->ctx->dmub_srv->idle_exit_counter) @@ -248,20 +244,6 @@ STATIC_IFN_KUNIT int dm_soft_reset(struct amdgpu_ip_block *ip_block) } EXPORT_IF_KUNIT(dm_soft_reset); -STATIC_IFN_KUNIT bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, - struct dm_crtc_state *new_state) -{ - if (new_state->stream->adjust.timing_adjust_pending) - return true; - if (new_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED) - return true; - else if (amdgpu_dm_crtc_vrr_active(old_state) != amdgpu_dm_crtc_vrr_active(new_state)) - return true; - else - return false; -} -EXPORT_IF_KUNIT(is_dc_timing_adjust_needed); - /* * DC will program planes with their z-order determined by their ordering * in the dc_surface_updates array. This comparator is used to sort them @@ -1542,7 +1524,7 @@ static void dm_destroy_cached_state(struct amdgpu_device *adev) for_each_new_crtc_in_state(dm->cached_state, crtc, new_crtc_state, i) { new_crtc_state->active_changed = true; dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); - reset_freesync_config_for_crtc(dm_new_crtc_state); + amdgpu_dm_reset_freesync_config_for_crtc(dm_new_crtc_state); } /* @@ -3614,213 +3596,6 @@ static void prepare_flip_isr(struct amdgpu_crtc *acrtc) acrtc->crtc_id); } -static void update_freesync_state_on_stream( - struct amdgpu_display_manager *dm, - struct dm_crtc_state *new_crtc_state, - struct dc_stream_state *new_stream, - struct dc_plane_state *surface, - u32 flip_timestamp_in_us) -{ - struct mod_vrr_params vrr_params; - struct dc_info_packet vrr_infopacket = {0}; - struct amdgpu_device *adev = dm->adev; - struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc); - unsigned long flags; - bool pack_sdp_v1_3 = false; - struct amdgpu_dm_connector *aconn; - enum vrr_packet_type packet_type = PACKET_TYPE_VRR; - - if (!new_stream) - return; - - /* - * TODO: Determine why min/max totals and vrefresh can be 0 here. - * For now it's sufficient to just guard against these conditions. - */ - - if (!new_stream->timing.h_total || !new_stream->timing.v_total) - return; - - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - vrr_params = acrtc->dm_irq_params.vrr_params; - - if (surface) { - mod_freesync_handle_preflip( - dm->freesync_module, - surface, - new_stream, - flip_timestamp_in_us, - &vrr_params); - - if (adev->family < AMDGPU_FAMILY_AI && - amdgpu_dm_crtc_vrr_active(new_crtc_state)) { - mod_freesync_handle_v_update(dm->freesync_module, - new_stream, &vrr_params); - - /* Need to call this before the frame ends. */ - dc_stream_adjust_vmin_vmax(dm->dc, - new_crtc_state->stream, - &vrr_params.adjust); - } - } - - aconn = (struct amdgpu_dm_connector *)new_stream->dm_stream_context; - - if (aconn && (aconn->as_type == FREESYNC_TYPE_PCON_IN_WHITELIST || aconn->vsdb_info.replay_mode)) { - pack_sdp_v1_3 = aconn->pack_sdp_v1_3; - - if (aconn->vsdb_info.amd_vsdb_version == 1) - packet_type = PACKET_TYPE_FS_V1; - else if (aconn->vsdb_info.amd_vsdb_version == 2) - packet_type = PACKET_TYPE_FS_V2; - else if (aconn->vsdb_info.amd_vsdb_version == 3) - packet_type = PACKET_TYPE_FS_V3; - - mod_build_adaptive_sync_infopacket(new_stream, aconn->as_type, NULL, - &new_stream->adaptive_sync_infopacket); - } - - mod_freesync_build_vrr_infopacket( - dm->freesync_module, - new_stream, - &vrr_params, - packet_type, - TRANSFER_FUNC_UNKNOWN, - &vrr_infopacket, - pack_sdp_v1_3); - - new_crtc_state->freesync_vrr_info_changed |= - (memcmp(&new_crtc_state->vrr_infopacket, - &vrr_infopacket, - sizeof(vrr_infopacket)) != 0); - - acrtc->dm_irq_params.vrr_params = vrr_params; - new_crtc_state->vrr_infopacket = vrr_infopacket; - - new_stream->vrr_infopacket = vrr_infopacket; - new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params); - - if (new_crtc_state->freesync_vrr_info_changed) - drm_dbg_kms(adev_to_drm(adev), "VRR packet update: crtc=%u enabled=%d state=%d", - new_crtc_state->base.crtc->base.id, - (int)new_crtc_state->base.vrr_enabled, - (int)vrr_params.state); - - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); -} - -static void update_stream_irq_parameters( - struct amdgpu_display_manager *dm, - struct dm_crtc_state *new_crtc_state) -{ - struct dc_stream_state *new_stream = new_crtc_state->stream; - struct mod_vrr_params vrr_params; - struct mod_freesync_config config = new_crtc_state->freesync_config; - struct amdgpu_device *adev = dm->adev; - struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc); - unsigned long flags; - - if (!new_stream) - return; - - /* - * TODO: Determine why min/max totals and vrefresh can be 0 here. - * For now it's sufficient to just guard against these conditions. - */ - if (!new_stream->timing.h_total || !new_stream->timing.v_total) - return; - - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - vrr_params = acrtc->dm_irq_params.vrr_params; - - if (new_crtc_state->vrr_supported && - config.min_refresh_in_uhz && - config.max_refresh_in_uhz) { - /* - * if freesync compatible mode was set, config.state will be set - * in atomic check - */ - if (config.state == VRR_STATE_ACTIVE_FIXED && config.fixed_refresh_in_uhz && - (!drm_atomic_crtc_needs_modeset(&new_crtc_state->base) || - new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED)) { - vrr_params.max_refresh_in_uhz = config.max_refresh_in_uhz; - vrr_params.min_refresh_in_uhz = config.min_refresh_in_uhz; - vrr_params.fixed_refresh_in_uhz = config.fixed_refresh_in_uhz; - vrr_params.state = VRR_STATE_ACTIVE_FIXED; - } else { - config.state = new_crtc_state->base.vrr_enabled ? - VRR_STATE_ACTIVE_VARIABLE : - VRR_STATE_INACTIVE; - } - } else { - config.state = VRR_STATE_UNSUPPORTED; - } - - mod_freesync_build_vrr_params(dm->freesync_module, - new_stream, - &config, &vrr_params); - - new_crtc_state->freesync_config = config; - /* Copy state for access from DM IRQ handler */ - acrtc->dm_irq_params.freesync_config = config; - acrtc->dm_irq_params.active_planes = new_crtc_state->active_planes; - acrtc->dm_irq_params.vrr_params = vrr_params; - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); -} - -static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, - struct dm_crtc_state *old_state, - struct dm_crtc_state *new_state) -{ - struct amdgpu_device *adev = dm->adev; - bool old_vrr_active = amdgpu_dm_crtc_vrr_active(old_state); - bool new_vrr_active = amdgpu_dm_crtc_vrr_active(new_state); - - /* Only DCE gates vupdate on VRR, keep it enabled for DCN */ - bool vrr_gates_vupdate = amdgpu_ip_version(adev, DCE_HWIP, 0) == 0; - - if (!old_vrr_active && new_vrr_active) { - /* Transition VRR inactive -> active: - * While VRR is active, we must not disable vblank irq, as a - * reenable after disable would compute bogus vblank/pflip - * timestamps if it likely happened inside display front-porch. - * - * We also need vupdate irq for the actual core vblank handling - * at end of vblank. - */ - if (vrr_gates_vupdate) - WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0); - WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0); - drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n", - __func__, new_state->base.crtc->base.id); - - scoped_guard(mutex, &dm->dc_lock) { - dc_exit_ips_for_hw_access(dm->dc); - amdgpu_dm_psr_set_event(dm, new_state->stream, true, - psr_event_vrr_transition, true); - amdgpu_dm_replay_set_event(dm, new_state->stream, true, - replay_event_vrr, true); - } - } else if (old_vrr_active && !new_vrr_active) { - /* Transition VRR active -> inactive: - * Allow vblank irq disable again for fixed refresh rate. - */ - if (vrr_gates_vupdate) - WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0); - drm_crtc_vblank_put(new_state->base.crtc); - drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n", - __func__, new_state->base.crtc->base.id); - - scoped_guard(mutex, &dm->dc_lock) { - dc_exit_ips_for_hw_access(dm->dc); - amdgpu_dm_psr_set_event(dm, new_state->stream, false, - psr_event_vrr_transition, false); - amdgpu_dm_replay_set_event(dm, new_state->stream, false, - replay_event_vrr, false); - } - } -} - static void amdgpu_dm_commit_cursors(struct drm_atomic_commit *state) { struct drm_plane *plane; @@ -4177,7 +3952,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, } if (plane == pcrtc->primary) - update_freesync_state_on_stream( + amdgpu_dm_update_freesync_state_on_stream( dm, acrtc_state, acrtc_state->stream, @@ -4295,7 +4070,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, * re-adjust the min/max bounds now that DC doesn't handle this * as part of commit. */ - if (is_dc_timing_adjust_needed(dm_old_crtc_state, acrtc_state)) { + if (amdgpu_dm_is_dc_timing_adjust_needed(dm_old_crtc_state, acrtc_state)) { spin_lock_irqsave(&pcrtc->dev->event_lock, flags); dc_stream_adjust_vmin_vmax( dm->dc, acrtc_state->stream, @@ -5346,7 +5121,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) dm_old_crtc_state = to_dm_crtc_state(old_crtc_state); /* For freesync config update on crtc state and params for irq */ - update_stream_irq_parameters(dm, dm_new_crtc_state); + amdgpu_dm_update_stream_irq_parameters(dm, dm_new_crtc_state); #ifdef CONFIG_DEBUG_FS spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); @@ -5546,108 +5321,6 @@ static int do_aquire_global_lock(struct drm_device *dev, return ret < 0 ? ret : 0; } -STATIC_IFN_KUNIT void get_freesync_config_for_crtc( - struct dm_crtc_state *new_crtc_state, - struct dm_connector_state *new_con_state) -{ - struct mod_freesync_config config = {0}; - struct amdgpu_dm_connector *aconnector; - struct drm_display_mode *mode = &new_crtc_state->base.mode; - int vrefresh = drm_mode_vrefresh(mode); - bool fs_vid_mode = false; - - if (new_con_state->base.connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - return; - - aconnector = to_amdgpu_dm_connector(new_con_state->base.connector); - - new_crtc_state->vrr_supported = new_con_state->freesync_capable && - vrefresh >= aconnector->min_vfreq && - vrefresh <= aconnector->max_vfreq; - - if (new_crtc_state->vrr_supported) { - new_crtc_state->stream->ignore_msa_timing_param = true; - fs_vid_mode = new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED; - - config.min_refresh_in_uhz = aconnector->min_vfreq * 1000000; - config.max_refresh_in_uhz = aconnector->max_vfreq * 1000000; - config.vsif_supported = true; - config.btr = true; - - if (fs_vid_mode) { - config.state = VRR_STATE_ACTIVE_FIXED; - config.fixed_refresh_in_uhz = new_crtc_state->freesync_config.fixed_refresh_in_uhz; - goto out; - } else if (new_crtc_state->base.vrr_enabled) { - config.state = VRR_STATE_ACTIVE_VARIABLE; - } else { - config.state = VRR_STATE_INACTIVE; - } - } else { - config.state = VRR_STATE_UNSUPPORTED; - } -out: - new_crtc_state->freesync_config = config; -} -EXPORT_IF_KUNIT(get_freesync_config_for_crtc); - -STATIC_IFN_KUNIT void reset_freesync_config_for_crtc( - struct dm_crtc_state *new_crtc_state) -{ - new_crtc_state->vrr_supported = false; - - memset(&new_crtc_state->vrr_infopacket, 0, - sizeof(new_crtc_state->vrr_infopacket)); -} -EXPORT_IF_KUNIT(reset_freesync_config_for_crtc); - -STATIC_IFN_KUNIT bool -is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, - struct drm_crtc_state *new_crtc_state) -{ - const struct drm_display_mode *old_mode, *new_mode; - - if (!old_crtc_state || !new_crtc_state) - return false; - - old_mode = &old_crtc_state->mode; - new_mode = &new_crtc_state->mode; - - if (old_mode->clock == new_mode->clock && - old_mode->hdisplay == new_mode->hdisplay && - old_mode->vdisplay == new_mode->vdisplay && - old_mode->htotal == new_mode->htotal && - old_mode->vtotal != new_mode->vtotal && - old_mode->hsync_start == new_mode->hsync_start && - old_mode->vsync_start != new_mode->vsync_start && - old_mode->hsync_end == new_mode->hsync_end && - old_mode->vsync_end != new_mode->vsync_end && - old_mode->hskew == new_mode->hskew && - old_mode->vscan == new_mode->vscan && - (old_mode->vsync_end - old_mode->vsync_start) == - (new_mode->vsync_end - new_mode->vsync_start)) - return true; - - return false; -} -EXPORT_IF_KUNIT(is_timing_unchanged_for_freesync); - -STATIC_IFN_KUNIT void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state) -{ - u64 num, den, res; - struct drm_crtc_state *new_crtc_state = &dm_new_crtc_state->base; - - dm_new_crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; - - num = (unsigned long long)new_crtc_state->mode.clock * 1000 * 1000000; - den = (unsigned long long)new_crtc_state->mode.htotal * - (unsigned long long)new_crtc_state->mode.vtotal; - - res = div_u64(num, den); - dm_new_crtc_state->freesync_config.fixed_refresh_in_uhz = res; -} -EXPORT_IF_KUNIT(set_freesync_fixed_config); - static int dm_update_crtc_state(struct amdgpu_display_manager *dm, struct drm_atomic_commit *state, struct drm_crtc *crtc, @@ -5744,7 +5417,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, */ if (amdgpu_freesync_vid_mode && dm_new_crtc_state->stream && - is_timing_unchanged_for_freesync(new_crtc_state, old_crtc_state)) + amdgpu_dm_is_timing_unchanged_for_freesync(new_crtc_state, old_crtc_state)) goto skip_modeset; if (dm_new_crtc_state->stream && @@ -5786,14 +5459,14 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, if (amdgpu_freesync_vid_mode && dm_new_crtc_state->stream && dc_is_stream_unchanged(new_stream, dm_old_crtc_state->stream) && dc_is_stream_scaling_unchanged(new_stream, dm_old_crtc_state->stream) && - is_timing_unchanged_for_freesync(new_crtc_state, + amdgpu_dm_is_timing_unchanged_for_freesync(new_crtc_state, old_crtc_state)) { new_crtc_state->mode_changed = false; drm_dbg_driver(adev_to_drm(adev), "Mode change not required for front porch change, setting mode_changed to %d", new_crtc_state->mode_changed); - set_freesync_fixed_config(dm_new_crtc_state); + amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state); goto skip_modeset; } else if (amdgpu_freesync_vid_mode && aconnector && @@ -5803,7 +5476,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false); if (!drm_mode_equal(&new_crtc_state->mode, high_mode)) - set_freesync_fixed_config(dm_new_crtc_state); + amdgpu_dm_set_freesync_fixed_config(dm_new_crtc_state); } ret = dm_atomic_get_state(state, &dm_state); @@ -5825,7 +5498,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, dc_stream_release(dm_old_crtc_state->stream); dm_new_crtc_state->stream = NULL; - reset_freesync_config_for_crtc(dm_new_crtc_state); + amdgpu_dm_reset_freesync_config_for_crtc(dm_new_crtc_state); *lock_and_validation_needed = true; @@ -5913,7 +5586,7 @@ skip_modeset: } /* Update Freesync settings. */ - get_freesync_config_for_crtc(dm_new_crtc_state, + amdgpu_dm_get_freesync_config_for_crtc(dm_new_crtc_state, dm_new_conn_state); return ret; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 70220997cde9..1ef92a28e3e9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1172,16 +1172,8 @@ void dm_get_plane_scale(struct drm_plane_state *plane_state, int *out_plane_scale_w, int *out_plane_scale_h); bool is_scaling_state_different(const struct dm_connector_state *dm_state, const struct dm_connector_state *old_dm_state); -bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, - struct drm_crtc_state *new_crtc_state); -void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state); -bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, - struct dm_crtc_state *new_state); void set_multisync_trigger_params(struct dc_stream_state *stream); void set_master_stream(struct dc_stream_state *stream_set[], int stream_count); -void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); -void get_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state, - struct dm_connector_state *new_con_state); void dm_enable_per_frame_crtc_master_sync(struct dc_state *context); struct hdcp_workqueue; bool is_content_protection_different(struct drm_crtc_state *new_crtc_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c new file mode 100644 index 000000000000..7e484adb4120 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#include +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_psr.h" +#include "amdgpu_dm_replay.h" +#include "amdgpu_dm_freesync.h" +#include "dm_helpers.h" +#include "modules/inc/mod_freesync.h" + +bool amdgpu_dm_is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, + struct dm_crtc_state *new_state) +{ + if (new_state->stream->adjust.timing_adjust_pending) + return true; + if (new_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED) + return true; + else if (amdgpu_dm_crtc_vrr_active(old_state) != amdgpu_dm_crtc_vrr_active(new_state)) + return true; + else + return false; +} +EXPORT_IF_KUNIT(amdgpu_dm_is_dc_timing_adjust_needed); + +bool +amdgpu_dm_is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, + struct drm_crtc_state *new_crtc_state) +{ + const struct drm_display_mode *old_mode, *new_mode; + + if (!old_crtc_state || !new_crtc_state) + return false; + + old_mode = &old_crtc_state->mode; + new_mode = &new_crtc_state->mode; + + if (old_mode->clock == new_mode->clock && + old_mode->hdisplay == new_mode->hdisplay && + old_mode->vdisplay == new_mode->vdisplay && + old_mode->htotal == new_mode->htotal && + old_mode->vtotal != new_mode->vtotal && + old_mode->hsync_start == new_mode->hsync_start && + old_mode->vsync_start != new_mode->vsync_start && + old_mode->hsync_end == new_mode->hsync_end && + old_mode->vsync_end != new_mode->vsync_end && + old_mode->hskew == new_mode->hskew && + old_mode->vscan == new_mode->vscan && + (old_mode->vsync_end - old_mode->vsync_start) == + (new_mode->vsync_end - new_mode->vsync_start)) + return true; + + return false; +} +EXPORT_IF_KUNIT(amdgpu_dm_is_timing_unchanged_for_freesync); + +void amdgpu_dm_set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state) +{ + u64 num, den, res; + struct drm_crtc_state *new_crtc_state = &dm_new_crtc_state->base; + + dm_new_crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; + + num = (unsigned long long)new_crtc_state->mode.clock * 1000 * 1000000; + den = (unsigned long long)new_crtc_state->mode.htotal * + (unsigned long long)new_crtc_state->mode.vtotal; + + res = div_u64(num, den); + dm_new_crtc_state->freesync_config.fixed_refresh_in_uhz = res; +} +EXPORT_IF_KUNIT(amdgpu_dm_set_freesync_fixed_config); + +void amdgpu_dm_reset_freesync_config_for_crtc( + struct dm_crtc_state *new_crtc_state) +{ + new_crtc_state->vrr_supported = false; + + memset(&new_crtc_state->vrr_infopacket, 0, + sizeof(new_crtc_state->vrr_infopacket)); +} +EXPORT_IF_KUNIT(amdgpu_dm_reset_freesync_config_for_crtc); + +void amdgpu_dm_get_freesync_config_for_crtc( + struct dm_crtc_state *new_crtc_state, + struct dm_connector_state *new_con_state) +{ + struct mod_freesync_config config = {0}; + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode *mode = &new_crtc_state->base.mode; + int vrefresh = drm_mode_vrefresh(mode); + bool fs_vid_mode = false; + + if (new_con_state->base.connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + return; + + aconnector = to_amdgpu_dm_connector(new_con_state->base.connector); + + new_crtc_state->vrr_supported = new_con_state->freesync_capable && + vrefresh >= aconnector->min_vfreq && + vrefresh <= aconnector->max_vfreq; + + if (new_crtc_state->vrr_supported) { + new_crtc_state->stream->ignore_msa_timing_param = true; + fs_vid_mode = new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED; + + config.min_refresh_in_uhz = aconnector->min_vfreq * 1000000; + config.max_refresh_in_uhz = aconnector->max_vfreq * 1000000; + config.vsif_supported = true; + config.btr = true; + + if (fs_vid_mode) { + config.state = VRR_STATE_ACTIVE_FIXED; + config.fixed_refresh_in_uhz = new_crtc_state->freesync_config.fixed_refresh_in_uhz; + goto out; + } else if (new_crtc_state->base.vrr_enabled) { + config.state = VRR_STATE_ACTIVE_VARIABLE; + } else { + config.state = VRR_STATE_INACTIVE; + } + } else { + config.state = VRR_STATE_UNSUPPORTED; + } +out: + new_crtc_state->freesync_config = config; +} +EXPORT_IF_KUNIT(amdgpu_dm_get_freesync_config_for_crtc); + +void amdgpu_dm_update_freesync_state_on_stream( + struct amdgpu_display_manager *dm, + struct dm_crtc_state *new_crtc_state, + struct dc_stream_state *new_stream, + struct dc_plane_state *surface, + u32 flip_timestamp_in_us) +{ + struct mod_vrr_params vrr_params; + struct dc_info_packet vrr_infopacket = {0}; + struct amdgpu_device *adev = dm->adev; + struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc); + unsigned long flags; + bool pack_sdp_v1_3 = false; + struct amdgpu_dm_connector *aconn; + enum vrr_packet_type packet_type = PACKET_TYPE_VRR; + + if (!new_stream) + return; + + /* + * TODO: Determine why min/max totals and vrefresh can be 0 here. + * For now it's sufficient to just guard against these conditions. + */ + + if (!new_stream->timing.h_total || !new_stream->timing.v_total) + return; + + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + vrr_params = acrtc->dm_irq_params.vrr_params; + + if (surface) { + mod_freesync_handle_preflip( + dm->freesync_module, + surface, + new_stream, + flip_timestamp_in_us, + &vrr_params); + + if (adev->family < AMDGPU_FAMILY_AI && + amdgpu_dm_crtc_vrr_active(new_crtc_state)) { + mod_freesync_handle_v_update(dm->freesync_module, + new_stream, &vrr_params); + + /* Need to call this before the frame ends. */ + dc_stream_adjust_vmin_vmax(dm->dc, + new_crtc_state->stream, + &vrr_params.adjust); + } + } + + aconn = (struct amdgpu_dm_connector *)new_stream->dm_stream_context; + + if (aconn && (aconn->as_type == FREESYNC_TYPE_PCON_IN_WHITELIST || aconn->vsdb_info.replay_mode)) { + pack_sdp_v1_3 = aconn->pack_sdp_v1_3; + + if (aconn->vsdb_info.amd_vsdb_version == 1) + packet_type = PACKET_TYPE_FS_V1; + else if (aconn->vsdb_info.amd_vsdb_version == 2) + packet_type = PACKET_TYPE_FS_V2; + else if (aconn->vsdb_info.amd_vsdb_version == 3) + packet_type = PACKET_TYPE_FS_V3; + + mod_build_adaptive_sync_infopacket(new_stream, aconn->as_type, NULL, + &new_stream->adaptive_sync_infopacket); + } + + mod_freesync_build_vrr_infopacket( + dm->freesync_module, + new_stream, + &vrr_params, + packet_type, + TRANSFER_FUNC_UNKNOWN, + &vrr_infopacket, + pack_sdp_v1_3); + + new_crtc_state->freesync_vrr_info_changed |= + (memcmp(&new_crtc_state->vrr_infopacket, + &vrr_infopacket, + sizeof(vrr_infopacket)) != 0); + + acrtc->dm_irq_params.vrr_params = vrr_params; + new_crtc_state->vrr_infopacket = vrr_infopacket; + + new_stream->vrr_infopacket = vrr_infopacket; + new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params); + + if (new_crtc_state->freesync_vrr_info_changed) + drm_dbg_kms(adev_to_drm(adev), "VRR packet update: crtc=%u enabled=%d state=%d", + new_crtc_state->base.crtc->base.id, + (int)new_crtc_state->base.vrr_enabled, + (int)vrr_params.state); + + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); +} + +void amdgpu_dm_update_stream_irq_parameters( + struct amdgpu_display_manager *dm, + struct dm_crtc_state *new_crtc_state) +{ + struct dc_stream_state *new_stream = new_crtc_state->stream; + struct mod_vrr_params vrr_params; + struct mod_freesync_config config = new_crtc_state->freesync_config; + struct amdgpu_device *adev = dm->adev; + struct amdgpu_crtc *acrtc = to_amdgpu_crtc(new_crtc_state->base.crtc); + unsigned long flags; + + if (!new_stream) + return; + + /* + * TODO: Determine why min/max totals and vrefresh can be 0 here. + * For now it's sufficient to just guard against these conditions. + */ + if (!new_stream->timing.h_total || !new_stream->timing.v_total) + return; + + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + vrr_params = acrtc->dm_irq_params.vrr_params; + + if (new_crtc_state->vrr_supported && + config.min_refresh_in_uhz && + config.max_refresh_in_uhz) { + /* + * if freesync compatible mode was set, config.state will be set + * in atomic check + */ + if (config.state == VRR_STATE_ACTIVE_FIXED && config.fixed_refresh_in_uhz && + (!drm_atomic_crtc_needs_modeset(&new_crtc_state->base) || + new_crtc_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED)) { + vrr_params.max_refresh_in_uhz = config.max_refresh_in_uhz; + vrr_params.min_refresh_in_uhz = config.min_refresh_in_uhz; + vrr_params.fixed_refresh_in_uhz = config.fixed_refresh_in_uhz; + vrr_params.state = VRR_STATE_ACTIVE_FIXED; + } else { + config.state = new_crtc_state->base.vrr_enabled ? + VRR_STATE_ACTIVE_VARIABLE : + VRR_STATE_INACTIVE; + } + } else { + config.state = VRR_STATE_UNSUPPORTED; + } + + mod_freesync_build_vrr_params(dm->freesync_module, + new_stream, + &config, &vrr_params); + + new_crtc_state->freesync_config = config; + /* Copy state for access from DM IRQ handler */ + acrtc->dm_irq_params.freesync_config = config; + acrtc->dm_irq_params.active_planes = new_crtc_state->active_planes; + acrtc->dm_irq_params.vrr_params = vrr_params; + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); +} + +void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, + struct dm_crtc_state *old_state, + struct dm_crtc_state *new_state) +{ + struct amdgpu_device *adev = dm->adev; + bool old_vrr_active = amdgpu_dm_crtc_vrr_active(old_state); + bool new_vrr_active = amdgpu_dm_crtc_vrr_active(new_state); + + /* Only DCE gates vupdate on VRR, keep it enabled for DCN */ + bool vrr_gates_vupdate = amdgpu_ip_version(adev, DCE_HWIP, 0) == 0; + + if (!old_vrr_active && new_vrr_active) { + /* Transition VRR inactive -> active: + * While VRR is active, we must not disable vblank irq, as a + * reenable after disable would compute bogus vblank/pflip + * timestamps if it likely happened inside display front-porch. + * + * We also need vupdate irq for the actual core vblank handling + * at end of vblank. + */ + if (vrr_gates_vupdate) + WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0); + WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0); + drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n", + __func__, new_state->base.crtc->base.id); + + scoped_guard(mutex, &dm->dc_lock) { + dc_exit_ips_for_hw_access(dm->dc); + amdgpu_dm_psr_set_event(dm, new_state->stream, true, + psr_event_vrr_transition, true); + amdgpu_dm_replay_set_event(dm, new_state->stream, true, + replay_event_vrr, true); + } + } else if (old_vrr_active && !new_vrr_active) { + /* Transition VRR active -> inactive: + * Allow vblank irq disable again for fixed refresh rate. + */ + if (vrr_gates_vupdate) + WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0); + drm_crtc_vblank_put(new_state->base.crtc); + drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n", + __func__, new_state->base.crtc->base.id); + + scoped_guard(mutex, &dm->dc_lock) { + dc_exit_ips_for_hw_access(dm->dc); + amdgpu_dm_psr_set_event(dm, new_state->stream, false, + psr_event_vrr_transition, false); + amdgpu_dm_replay_set_event(dm, new_state->stream, false, + replay_event_vrr, false); + } + } +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h new file mode 100644 index 000000000000..24c410306284 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_freesync.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#ifndef __AMDGPU_DM_FREESYNC_H__ +#define __AMDGPU_DM_FREESYNC_H__ + +#include + +struct amdgpu_display_manager; +struct dm_crtc_state; +struct dm_connector_state; +struct dc_stream_state; +struct dc_plane_state; +struct drm_crtc_state; + +bool amdgpu_dm_is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, + struct dm_crtc_state *new_state); + +bool amdgpu_dm_is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, + struct drm_crtc_state *new_crtc_state); + +void amdgpu_dm_set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state); + +void amdgpu_dm_reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); + +void amdgpu_dm_get_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state, + struct dm_connector_state *new_con_state); + +void amdgpu_dm_update_freesync_state_on_stream(struct amdgpu_display_manager *dm, + struct dm_crtc_state *new_crtc_state, + struct dc_stream_state *new_stream, + struct dc_plane_state *surface, + u32 flip_timestamp_in_us); + +void amdgpu_dm_update_stream_irq_parameters(struct amdgpu_display_manager *dm, + struct dm_crtc_state *new_crtc_state); + +void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, + struct dm_crtc_state *old_state, + struct dm_crtc_state *new_state); + +#endif /* __AMDGPU_DM_FREESYNC_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 1592e8dae1a9..e58ffc4dcd84 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_plane_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_freesync_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c new file mode 100644 index 000000000000..0ae636dcbbe4 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_freesync_test.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_freesync.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include + +#include "dc.h" +#include "inc/core_types.h" +#include "amd_shared.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_freesync.h" +#include "amdgpu_dm_kunit_test_helpers.h" + +/* Tests for amdgpu_dm_is_timing_unchanged_for_freesync() */ + +/** + * dm_test_timing_unchanged_null_args - Test NULL crtc states return false + * @test: The KUnit test context + */ +static void dm_test_timing_unchanged_null_args(struct kunit *test) +{ + struct drm_crtc_state crtc_state = { 0 }; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_is_timing_unchanged_for_freesync(NULL, &crtc_state)); + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_is_timing_unchanged_for_freesync(&crtc_state, NULL)); +} + +/** + * dm_test_timing_unchanged_identical_modes - Test identical modes are not "unchanged" + * @test: The KUnit test context + * + * The helper only returns true when vtotal/vsync shift (vrr) while the rest + * of the timing stays fixed, so identical modes must return false. + */ +static void dm_test_timing_unchanged_identical_modes(struct kunit *test) +{ + struct drm_crtc_state old_state = { 0 }; + struct drm_crtc_state new_state = { 0 }; + + old_state.mode.clock = 148500; + old_state.mode.hdisplay = 1920; + old_state.mode.vdisplay = 1080; + old_state.mode.htotal = 2200; + old_state.mode.vtotal = 1125; + new_state.mode = old_state.mode; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_is_timing_unchanged_for_freesync(&old_state, &new_state)); +} + +/** + * dm_test_timing_unchanged_vrr_shift - Test vrr-style vtotal/vsync shift is detected + * @test: The KUnit test context + */ +static void dm_test_timing_unchanged_vrr_shift(struct kunit *test) +{ + struct drm_crtc_state old_state = { 0 }; + struct drm_crtc_state new_state = { 0 }; + + old_state.mode.clock = 148500; + old_state.mode.hdisplay = 1920; + old_state.mode.vdisplay = 1080; + old_state.mode.htotal = 2200; + old_state.mode.vtotal = 1125; + old_state.mode.hsync_start = 2008; + old_state.mode.vsync_start = 1084; + old_state.mode.hsync_end = 2052; + old_state.mode.vsync_end = 1089; + + /* Same horizontal timing, vertical totals/sync shifted by 125 lines */ + new_state.mode = old_state.mode; + new_state.mode.vtotal = 1250; + new_state.mode.vsync_start = 1209; + new_state.mode.vsync_end = 1214; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_is_timing_unchanged_for_freesync(&old_state, &new_state)); +} + +/** + * dm_test_timing_unchanged_clock_changed - Test pixel clock change returns false + * @test: The KUnit test context + */ +static void dm_test_timing_unchanged_clock_changed(struct kunit *test) +{ + struct drm_crtc_state old_state = { 0 }; + struct drm_crtc_state new_state = { 0 }; + + old_state.mode.clock = 148500; + old_state.mode.htotal = 2200; + old_state.mode.vtotal = 1125; + old_state.mode.vsync_start = 1084; + old_state.mode.vsync_end = 1089; + + new_state.mode = old_state.mode; + new_state.mode.clock = 297000; + new_state.mode.vtotal = 1250; + new_state.mode.vsync_start = 1209; + new_state.mode.vsync_end = 1214; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_is_timing_unchanged_for_freesync(&old_state, &new_state)); +} + +/* Tests for amdgpu_dm_set_freesync_fixed_config() */ + +/** + * dm_test_set_freesync_fixed_config_60hz - Test fixed refresh computed for 1080p60 + * @test: The KUnit test context + */ +static void dm_test_set_freesync_fixed_config_60hz(struct kunit *test) +{ + struct dm_crtc_state dm_crtc_state = { 0 }; + + dm_crtc_state.base.mode.clock = 148500; + dm_crtc_state.base.mode.htotal = 2200; + dm_crtc_state.base.mode.vtotal = 1125; + + amdgpu_dm_set_freesync_fixed_config(&dm_crtc_state); + + KUNIT_EXPECT_EQ(test, (int)dm_crtc_state.freesync_config.state, + (int)VRR_STATE_ACTIVE_FIXED); + /* 148500 kHz / (2200 * 1125) = 60 Hz = 60000000 uHz */ + KUNIT_EXPECT_EQ(test, dm_crtc_state.freesync_config.fixed_refresh_in_uhz, + 60000000U); +} + +/* Tests for amdgpu_dm_is_dc_timing_adjust_needed() */ + +/** + * dm_test_dc_timing_adjust_pending - Test a pending hw timing adjust forces true + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_pending(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + stream->adjust.timing_adjust_pending = 1; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state)); +} + +/** + * dm_test_dc_timing_adjust_active_fixed - Test VRR active-fixed forces true + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_active_fixed(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + new_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state)); +} + +/** + * dm_test_dc_timing_adjust_vrr_toggle - Test a change in vrr active state forces true + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_vrr_toggle(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + old_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + new_state->freesync_config.state = VRR_STATE_INACTIVE; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state)); +} + +/** + * dm_test_dc_timing_adjust_not_needed - Test steady-state timing needs no adjust + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_not_needed(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + old_state->freesync_config.state = VRR_STATE_INACTIVE; + new_state->freesync_config.state = VRR_STATE_INACTIVE; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_dc_timing_adjust_needed(old_state, new_state)); +} + +/* Tests for amdgpu_dm_get_freesync_config_for_crtc() */ + +struct dm_test_freesync_ctx { + struct amdgpu_dm_connector *aconnector; + struct dm_crtc_state *crtc_state; + struct dm_connector_state *conn_state; + struct dc_stream_state *stream; +}; + +static struct dm_test_freesync_ctx *dm_test_freesync_ctx_alloc(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); + ctx->crtc_state = kunit_kzalloc(test, sizeof(*ctx->crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->crtc_state); + ctx->conn_state = kunit_kzalloc(test, sizeof(*ctx->conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state); + ctx->stream = dm_kunit_alloc_stream(test, NULL); + + ctx->conn_state->base.connector = &ctx->aconnector->base; + ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_DisplayPort; + ctx->crtc_state->stream = ctx->stream; + + /* 1080p60 timing so drm_mode_vrefresh() == 60 */ + ctx->crtc_state->base.mode.clock = 148500; + ctx->crtc_state->base.mode.htotal = 2200; + ctx->crtc_state->base.mode.vtotal = 1125; + + return ctx; +} + +/** + * dm_test_freesync_config_writeback - Test writeback connector is left untouched + * @test: The KUnit test context + */ +static void dm_test_freesync_config_writeback(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + ctx->crtc_state->vrr_supported = true; /* sentinel: must stay set */ + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + /* Writeback: early return leaves vrr_supported sentinel untouched */ + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); +} + +/** + * dm_test_freesync_config_not_capable - Test a non-freesync sink reports UNSUPPORTED + * @test: The KUnit test context + */ +static void dm_test_freesync_config_not_capable(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = false; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_UNSUPPORTED); +} + +/** + * dm_test_freesync_config_out_of_range - Test a refresh outside the range is UNSUPPORTED + * @test: The KUnit test context + */ +static void dm_test_freesync_config_out_of_range(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 90; /* 60 < 90 -> out of range */ + ctx->aconnector->max_vfreq = 120; + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_UNSUPPORTED); +} + +/** + * dm_test_freesync_config_active_variable - Test vrr_enabled yields ACTIVE_VARIABLE + * @test: The KUnit test context + */ +static void dm_test_freesync_config_active_variable(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + ctx->crtc_state->base.vrr_enabled = true; + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_TRUE(test, ctx->stream->ignore_msa_timing_param); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_ACTIVE_VARIABLE); + KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.min_refresh_in_uhz, + 48000000U); + KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.max_refresh_in_uhz, + 120000000U); + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.vsif_supported); + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.btr); +} + +/** + * dm_test_freesync_config_inactive - Test supported-but-off yields INACTIVE + * @test: The KUnit test context + */ +static void dm_test_freesync_config_inactive(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + ctx->crtc_state->base.vrr_enabled = false; + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_INACTIVE); +} + +/** + * dm_test_freesync_config_active_fixed - Test freesync-video mode yields ACTIVE_FIXED + * @test: The KUnit test context + */ +static void dm_test_freesync_config_active_fixed(struct kunit *test) +{ + struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); + + ctx->conn_state->freesync_capable = true; + ctx->aconnector->min_vfreq = 48; + ctx->aconnector->max_vfreq = 120; + /* Pre-set fixed state selects the freesync-video (fixed) path */ + ctx->crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; + ctx->crtc_state->freesync_config.fixed_refresh_in_uhz = 60000000; + ctx->crtc_state->base.vrr_enabled = true; /* ignored on the fixed path */ + + amdgpu_dm_get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); + + KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); + KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, + (int)VRR_STATE_ACTIVE_FIXED); + KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.fixed_refresh_in_uhz, + 60000000U); +} + +/* Tests for amdgpu_dm_reset_freesync_config_for_crtc() */ + +/** + * dm_test_reset_freesync_config - Test reset clears vrr support and info packet + * @test: The KUnit test context + */ +static void dm_test_reset_freesync_config(struct kunit *test) +{ + struct dm_crtc_state *crtc_state; + + crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc_state); + + crtc_state->vrr_supported = true; + crtc_state->vrr_infopacket.valid = true; + + amdgpu_dm_reset_freesync_config_for_crtc(crtc_state); + + KUNIT_EXPECT_FALSE(test, crtc_state->vrr_supported); + KUNIT_EXPECT_FALSE(test, crtc_state->vrr_infopacket.valid); +} + +static struct kunit_case amdgpu_dm_freesync_tests[] = { + /* amdgpu_dm_is_timing_unchanged_for_freesync */ + KUNIT_CASE(dm_test_timing_unchanged_null_args), + KUNIT_CASE(dm_test_timing_unchanged_identical_modes), + KUNIT_CASE(dm_test_timing_unchanged_vrr_shift), + KUNIT_CASE(dm_test_timing_unchanged_clock_changed), + /* amdgpu_dm_set_freesync_fixed_config */ + KUNIT_CASE(dm_test_set_freesync_fixed_config_60hz), + /* amdgpu_dm_is_dc_timing_adjust_needed */ + KUNIT_CASE(dm_test_dc_timing_adjust_pending), + KUNIT_CASE(dm_test_dc_timing_adjust_active_fixed), + KUNIT_CASE(dm_test_dc_timing_adjust_vrr_toggle), + KUNIT_CASE(dm_test_dc_timing_adjust_not_needed), + /* amdgpu_dm_get_freesync_config_for_crtc */ + KUNIT_CASE(dm_test_freesync_config_writeback), + KUNIT_CASE(dm_test_freesync_config_not_capable), + KUNIT_CASE(dm_test_freesync_config_out_of_range), + KUNIT_CASE(dm_test_freesync_config_active_variable), + KUNIT_CASE(dm_test_freesync_config_inactive), + KUNIT_CASE(dm_test_freesync_config_active_fixed), + /* amdgpu_dm_reset_freesync_config_for_crtc */ + KUNIT_CASE(dm_test_reset_freesync_config), + {} +}; + +static struct kunit_suite amdgpu_dm_freesync_test_suite = { + .name = "amdgpu_dm_freesync", + .test_cases = amdgpu_dm_freesync_tests, +}; + +kunit_test_suite(amdgpu_dm_freesync_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_freesync"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 49a1d2ae5588..428d24259198 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -860,214 +860,6 @@ static void dm_test_scaling_state_underscan_border_changed(struct kunit *test) KUNIT_EXPECT_TRUE(test, is_scaling_state_different(a, b)); } -/* Tests for is_timing_unchanged_for_freesync() */ - -/** - * dm_test_timing_unchanged_null_args - Test NULL crtc states return false - * @test: The KUnit test context - */ -static void dm_test_timing_unchanged_null_args(struct kunit *test) -{ - struct drm_crtc_state crtc_state = { 0 }; - - KUNIT_EXPECT_FALSE(test, - is_timing_unchanged_for_freesync(NULL, &crtc_state)); - KUNIT_EXPECT_FALSE(test, - is_timing_unchanged_for_freesync(&crtc_state, NULL)); -} - -/** - * dm_test_timing_unchanged_identical_modes - Test identical modes are not "unchanged" - * @test: The KUnit test context - * - * The helper only returns true when vtotal/vsync shift (vrr) while the rest - * of the timing stays fixed, so identical modes must return false. - */ -static void dm_test_timing_unchanged_identical_modes(struct kunit *test) -{ - struct drm_crtc_state old_state = { 0 }; - struct drm_crtc_state new_state = { 0 }; - - old_state.mode.clock = 148500; - old_state.mode.hdisplay = 1920; - old_state.mode.vdisplay = 1080; - old_state.mode.htotal = 2200; - old_state.mode.vtotal = 1125; - new_state.mode = old_state.mode; - - KUNIT_EXPECT_FALSE(test, - is_timing_unchanged_for_freesync(&old_state, &new_state)); -} - -/** - * dm_test_timing_unchanged_vrr_shift - Test vrr-style vtotal/vsync shift is detected - * @test: The KUnit test context - */ -static void dm_test_timing_unchanged_vrr_shift(struct kunit *test) -{ - struct drm_crtc_state old_state = { 0 }; - struct drm_crtc_state new_state = { 0 }; - - old_state.mode.clock = 148500; - old_state.mode.hdisplay = 1920; - old_state.mode.vdisplay = 1080; - old_state.mode.htotal = 2200; - old_state.mode.vtotal = 1125; - old_state.mode.hsync_start = 2008; - old_state.mode.vsync_start = 1084; - old_state.mode.hsync_end = 2052; - old_state.mode.vsync_end = 1089; - - /* Same horizontal timing, vertical totals/sync shifted by 125 lines */ - new_state.mode = old_state.mode; - new_state.mode.vtotal = 1250; - new_state.mode.vsync_start = 1209; - new_state.mode.vsync_end = 1214; - - KUNIT_EXPECT_TRUE(test, - is_timing_unchanged_for_freesync(&old_state, &new_state)); -} - -/** - * dm_test_timing_unchanged_clock_changed - Test pixel clock change returns false - * @test: The KUnit test context - */ -static void dm_test_timing_unchanged_clock_changed(struct kunit *test) -{ - struct drm_crtc_state old_state = { 0 }; - struct drm_crtc_state new_state = { 0 }; - - old_state.mode.clock = 148500; - old_state.mode.htotal = 2200; - old_state.mode.vtotal = 1125; - old_state.mode.vsync_start = 1084; - old_state.mode.vsync_end = 1089; - - new_state.mode = old_state.mode; - new_state.mode.clock = 297000; - new_state.mode.vtotal = 1250; - new_state.mode.vsync_start = 1209; - new_state.mode.vsync_end = 1214; - - KUNIT_EXPECT_FALSE(test, - is_timing_unchanged_for_freesync(&old_state, &new_state)); -} - -/* Tests for set_freesync_fixed_config() */ - -/** - * dm_test_set_freesync_fixed_config_60hz - Test fixed refresh computed for 1080p60 - * @test: The KUnit test context - */ -static void dm_test_set_freesync_fixed_config_60hz(struct kunit *test) -{ - struct dm_crtc_state dm_crtc_state = { 0 }; - - dm_crtc_state.base.mode.clock = 148500; - dm_crtc_state.base.mode.htotal = 2200; - dm_crtc_state.base.mode.vtotal = 1125; - - set_freesync_fixed_config(&dm_crtc_state); - - KUNIT_EXPECT_EQ(test, (int)dm_crtc_state.freesync_config.state, - (int)VRR_STATE_ACTIVE_FIXED); - /* 148500 kHz / (2200 * 1125) = 60 Hz = 60000000 uHz */ - KUNIT_EXPECT_EQ(test, dm_crtc_state.freesync_config.fixed_refresh_in_uhz, - 60000000U); -} - -/* Tests for is_dc_timing_adjust_needed() */ - -/** - * dm_test_dc_timing_adjust_pending - Test a pending hw timing adjust forces true - * @test: The KUnit test context - */ -static void dm_test_dc_timing_adjust_pending(struct kunit *test) -{ - struct dm_crtc_state *old_state, *new_state; - struct dc_stream_state *stream; - - old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); - new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); - - new_state->stream = stream; - stream->adjust.timing_adjust_pending = 1; - - KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state)); -} - -/** - * dm_test_dc_timing_adjust_active_fixed - Test VRR active-fixed forces true - * @test: The KUnit test context - */ -static void dm_test_dc_timing_adjust_active_fixed(struct kunit *test) -{ - struct dm_crtc_state *old_state, *new_state; - struct dc_stream_state *stream; - - old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); - new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); - - new_state->stream = stream; - new_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; - - KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state)); -} - -/** - * dm_test_dc_timing_adjust_vrr_toggle - Test a change in vrr active state forces true - * @test: The KUnit test context - */ -static void dm_test_dc_timing_adjust_vrr_toggle(struct kunit *test) -{ - struct dm_crtc_state *old_state, *new_state; - struct dc_stream_state *stream; - - old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); - new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); - - new_state->stream = stream; - old_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; - new_state->freesync_config.state = VRR_STATE_INACTIVE; - - KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state)); -} - -/** - * dm_test_dc_timing_adjust_not_needed - Test steady-state timing needs no adjust - * @test: The KUnit test context - */ -static void dm_test_dc_timing_adjust_not_needed(struct kunit *test) -{ - struct dm_crtc_state *old_state, *new_state; - struct dc_stream_state *stream; - - old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); - new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); - - new_state->stream = stream; - old_state->freesync_config.state = VRR_STATE_INACTIVE; - new_state->freesync_config.state = VRR_STATE_INACTIVE; - - KUNIT_EXPECT_FALSE(test, is_dc_timing_adjust_needed(old_state, new_state)); -} - /* Tests for set_multisync_trigger_params() */ /** @@ -1435,194 +1227,6 @@ static void dm_test_cp_diff_desired_to_undesired(struct kunit *test) KUNIT_EXPECT_TRUE(test, dm_test_cp_diff(ctx)); } -/* Tests for get_freesync_config_for_crtc() */ - -struct dm_test_freesync_ctx { - struct amdgpu_dm_connector *aconnector; - struct dm_crtc_state *crtc_state; - struct dm_connector_state *conn_state; - struct dc_stream_state *stream; -}; - -static struct dm_test_freesync_ctx *dm_test_freesync_ctx_alloc(struct kunit *test) -{ - struct dm_test_freesync_ctx *ctx; - - ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx); - - ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); - ctx->crtc_state = kunit_kzalloc(test, sizeof(*ctx->crtc_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->crtc_state); - ctx->conn_state = kunit_kzalloc(test, sizeof(*ctx->conn_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state); - ctx->stream = dm_kunit_alloc_stream(test, NULL); - - ctx->conn_state->base.connector = &ctx->aconnector->base; - ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_DisplayPort; - ctx->crtc_state->stream = ctx->stream; - - /* 1080p60 timing so drm_mode_vrefresh() == 60 */ - ctx->crtc_state->base.mode.clock = 148500; - ctx->crtc_state->base.mode.htotal = 2200; - ctx->crtc_state->base.mode.vtotal = 1125; - - return ctx; -} - -/** - * dm_test_freesync_config_writeback - Test writeback connector is left untouched - * @test: The KUnit test context - */ -static void dm_test_freesync_config_writeback(struct kunit *test) -{ - struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); - - ctx->aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; - ctx->conn_state->freesync_capable = true; - ctx->aconnector->min_vfreq = 48; - ctx->aconnector->max_vfreq = 120; - ctx->crtc_state->vrr_supported = true; /* sentinel: must stay set */ - - get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); - - /* Writeback: early return leaves vrr_supported sentinel untouched */ - KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); -} - -/** - * dm_test_freesync_config_not_capable - Test a non-freesync sink reports UNSUPPORTED - * @test: The KUnit test context - */ -static void dm_test_freesync_config_not_capable(struct kunit *test) -{ - struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); - - ctx->conn_state->freesync_capable = false; - ctx->aconnector->min_vfreq = 48; - ctx->aconnector->max_vfreq = 120; - - get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); - - KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported); - KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, - (int)VRR_STATE_UNSUPPORTED); -} - -/** - * dm_test_freesync_config_out_of_range - Test a refresh outside the range is UNSUPPORTED - * @test: The KUnit test context - */ -static void dm_test_freesync_config_out_of_range(struct kunit *test) -{ - struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); - - ctx->conn_state->freesync_capable = true; - ctx->aconnector->min_vfreq = 90; /* 60 < 90 -> out of range */ - ctx->aconnector->max_vfreq = 120; - - get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); - - KUNIT_EXPECT_FALSE(test, ctx->crtc_state->vrr_supported); - KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, - (int)VRR_STATE_UNSUPPORTED); -} - -/** - * dm_test_freesync_config_active_variable - Test vrr_enabled yields ACTIVE_VARIABLE - * @test: The KUnit test context - */ -static void dm_test_freesync_config_active_variable(struct kunit *test) -{ - struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); - - ctx->conn_state->freesync_capable = true; - ctx->aconnector->min_vfreq = 48; - ctx->aconnector->max_vfreq = 120; - ctx->crtc_state->base.vrr_enabled = true; - - get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); - - KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); - KUNIT_EXPECT_TRUE(test, ctx->stream->ignore_msa_timing_param); - KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, - (int)VRR_STATE_ACTIVE_VARIABLE); - KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.min_refresh_in_uhz, - 48000000U); - KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.max_refresh_in_uhz, - 120000000U); - KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.vsif_supported); - KUNIT_EXPECT_TRUE(test, ctx->crtc_state->freesync_config.btr); -} - -/** - * dm_test_freesync_config_inactive - Test supported-but-off yields INACTIVE - * @test: The KUnit test context - */ -static void dm_test_freesync_config_inactive(struct kunit *test) -{ - struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); - - ctx->conn_state->freesync_capable = true; - ctx->aconnector->min_vfreq = 48; - ctx->aconnector->max_vfreq = 120; - ctx->crtc_state->base.vrr_enabled = false; - - get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); - - KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); - KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, - (int)VRR_STATE_INACTIVE); -} - -/** - * dm_test_freesync_config_active_fixed - Test freesync-video mode yields ACTIVE_FIXED - * @test: The KUnit test context - */ -static void dm_test_freesync_config_active_fixed(struct kunit *test) -{ - struct dm_test_freesync_ctx *ctx = dm_test_freesync_ctx_alloc(test); - - ctx->conn_state->freesync_capable = true; - ctx->aconnector->min_vfreq = 48; - ctx->aconnector->max_vfreq = 120; - /* Pre-set fixed state selects the freesync-video (fixed) path */ - ctx->crtc_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; - ctx->crtc_state->freesync_config.fixed_refresh_in_uhz = 60000000; - ctx->crtc_state->base.vrr_enabled = true; /* ignored on the fixed path */ - - get_freesync_config_for_crtc(ctx->crtc_state, ctx->conn_state); - - KUNIT_EXPECT_TRUE(test, ctx->crtc_state->vrr_supported); - KUNIT_EXPECT_EQ(test, (int)ctx->crtc_state->freesync_config.state, - (int)VRR_STATE_ACTIVE_FIXED); - KUNIT_EXPECT_EQ(test, ctx->crtc_state->freesync_config.fixed_refresh_in_uhz, - 60000000U); -} - -/* Tests for reset_freesync_config_for_crtc() */ - -/** - * dm_test_reset_freesync_config - Test reset clears vrr support and info packet - * @test: The KUnit test context - */ -static void dm_test_reset_freesync_config(struct kunit *test) -{ - struct dm_crtc_state *crtc_state; - - crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, crtc_state); - - crtc_state->vrr_supported = true; - crtc_state->vrr_infopacket.valid = true; - - reset_freesync_config_for_crtc(crtc_state); - - KUNIT_EXPECT_FALSE(test, crtc_state->vrr_supported); - KUNIT_EXPECT_FALSE(test, crtc_state->vrr_infopacket.valid); -} - /* Tests for dm_enable_per_frame_crtc_master_sync() */ /** @@ -1818,18 +1422,6 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_scaling_state_underscan_enabled), KUNIT_CASE(dm_test_scaling_state_underscan_disabled), KUNIT_CASE(dm_test_scaling_state_underscan_border_changed), - /* is_timing_unchanged_for_freesync */ - KUNIT_CASE(dm_test_timing_unchanged_null_args), - KUNIT_CASE(dm_test_timing_unchanged_identical_modes), - KUNIT_CASE(dm_test_timing_unchanged_vrr_shift), - KUNIT_CASE(dm_test_timing_unchanged_clock_changed), - /* set_freesync_fixed_config */ - KUNIT_CASE(dm_test_set_freesync_fixed_config_60hz), - /* is_dc_timing_adjust_needed */ - KUNIT_CASE(dm_test_dc_timing_adjust_pending), - KUNIT_CASE(dm_test_dc_timing_adjust_active_fixed), - KUNIT_CASE(dm_test_dc_timing_adjust_vrr_toggle), - KUNIT_CASE(dm_test_dc_timing_adjust_not_needed), /* set_multisync_trigger_params */ KUNIT_CASE(dm_test_multisync_trigger_disabled), KUNIT_CASE(dm_test_multisync_trigger_rising), @@ -1849,15 +1441,6 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_cp_diff_s3_undesired_to_enabled), KUNIT_CASE(dm_test_cp_diff_desired_to_enabled), KUNIT_CASE(dm_test_cp_diff_desired_to_undesired), - /* get_freesync_config_for_crtc */ - KUNIT_CASE(dm_test_freesync_config_writeback), - KUNIT_CASE(dm_test_freesync_config_not_capable), - KUNIT_CASE(dm_test_freesync_config_out_of_range), - KUNIT_CASE(dm_test_freesync_config_active_variable), - KUNIT_CASE(dm_test_freesync_config_inactive), - KUNIT_CASE(dm_test_freesync_config_active_fixed), - /* reset_freesync_config_for_crtc */ - KUNIT_CASE(dm_test_reset_freesync_config), /* dm_enable_per_frame_crtc_master_sync */ KUNIT_CASE(dm_test_per_frame_master_sync_single_stream), KUNIT_CASE(dm_test_per_frame_master_sync_two_streams), -- cgit v1.2.3 From 87a6dab12fc0695dce9c3ba0bb35714c92f26f32 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 9 Jul 2026 21:19:12 -0600 Subject: drm/amd/display: add cursor module [WHAT] Group the cursor policy and validation helpers from amdgpu_dm.c into a dedicated amdgpu_dm_cursor.c and amdgpu_dm_cursor.h. The moved functions are: dm_check_cursor_fb, dm_check_native_cursor_state, dm_should_update_native_cursor, dm_get_oriented_plane_size, dm_get_plane_scale, dm_plane_color_pipeline_active, dm_crtc_get_cursor_mode. The cursor helpers exposed by amdgpu_dm_cursor.h are called from amdgpu_dm.c, so give the three that lacked a namespace prefix the standard amdgpu_dm_ prefix: amdgpu_dm_check_native_cursor_state, amdgpu_dm_should_update_native_cursor and amdgpu_dm_crtc_get_cursor_mode. The descending-zpos plane iterator shared by these functions and amdgpu_dm_atomic_check (the for_each_oldnew_plane_in_descending_zpos macro and its __get_next_zpos helper) moves to amdgpu_dm.h, with the helper renamed amdgpu_dm_get_next_zpos and made non-static. Relocate the corresponding KUnit tests into amdgpu_dm_cursor_test.c. No functional change. Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 436 +------------------- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 24 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c | 443 +++++++++++++++++++++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h | 52 +++ .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_cursor_test.c | 268 +++++++++++++ .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 232 ----------- 8 files changed, 796 insertions(+), 663 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index 5cb8c143e72f..d1faf2d8370f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -46,7 +46,8 @@ AMDGPUDM = \ amdgpu_dm_audio.o \ amdgpu_dm_dmub.o \ amdgpu_dm_connector.o \ - amdgpu_dm_freesync.o + amdgpu_dm_freesync.o \ + amdgpu_dm_cursor.o ifdef CONFIG_DRM_AMD_DC_FP AMDGPUDM += dc_fpu.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 09281e003a43..979149ded1c2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -72,6 +72,7 @@ #include "amdgpu_dm_connector.h" #include "amdgpu_dm_pp_smu.h" #include "amdgpu_dm_freesync.h" +#include "amdgpu_dm_cursor.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -5758,133 +5759,6 @@ static bool should_reset_plane(struct drm_atomic_commit *state, return false; } -static int dm_check_cursor_fb(struct amdgpu_crtc *new_acrtc, - struct drm_plane_state *new_plane_state, - struct drm_framebuffer *fb) -{ - struct amdgpu_device *adev = drm_to_adev(new_acrtc->base.dev); - struct amdgpu_framebuffer *afb = to_amdgpu_framebuffer(fb); - unsigned int pitch; - bool linear; - - if (fb->width > new_acrtc->max_cursor_width || - fb->height > new_acrtc->max_cursor_height) { - drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB size %dx%d\n", - new_plane_state->fb->width, - new_plane_state->fb->height); - return -EINVAL; - } - if (new_plane_state->src_w != fb->width << 16 || - new_plane_state->src_h != fb->height << 16) { - drm_dbg_atomic(adev_to_drm(adev), "Cropping not supported for cursor plane\n"); - return -EINVAL; - } - - /* Pitch in pixels */ - pitch = fb->pitches[0] / fb->format->cpp[0]; - - if (fb->width != pitch) { - drm_dbg_atomic(adev_to_drm(adev), "Cursor FB width %d doesn't match pitch %d", - fb->width, pitch); - return -EINVAL; - } - - switch (pitch) { - case 64: - case 128: - case 256: - /* FB pitch is supported by cursor plane */ - break; - default: - drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB pitch %d px\n", pitch); - return -EINVAL; - } - - /* Core DRM takes care of checking FB modifiers, so we only need to - * check tiling flags when the FB doesn't have a modifier. - */ - if (!(fb->flags & DRM_MODE_FB_MODIFIERS)) { - if (adev->family == AMDGPU_FAMILY_GC_12_0_0) { - linear = AMDGPU_TILING_GET(afb->tiling_flags, GFX12_SWIZZLE_MODE) == 0; - } else if (adev->family >= AMDGPU_FAMILY_AI) { - linear = AMDGPU_TILING_GET(afb->tiling_flags, SWIZZLE_MODE) == 0; - } else { - linear = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_2D_TILED_THIN1 && - AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_1D_TILED_THIN1 && - AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE) == 0; - } - if (!linear) { - drm_dbg_atomic(adev_to_drm(adev), "Cursor FB not linear"); - return -EINVAL; - } - } - - return 0; -} - -/* - * Helper function for checking the cursor in native mode - */ -static int dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc, - struct drm_plane *plane, - struct drm_plane_state *new_plane_state, - bool enable) -{ - - struct amdgpu_crtc *new_acrtc; - int ret; - - if (!enable || !new_plane_crtc || - drm_atomic_plane_disabling(plane->state, new_plane_state)) - return 0; - - new_acrtc = to_amdgpu_crtc(new_plane_crtc); - - if (new_plane_state->src_x != 0 || new_plane_state->src_y != 0) { - drm_dbg_atomic(new_plane_crtc->dev, "Cropping not supported for cursor plane\n"); - return -EINVAL; - } - - if (new_plane_state->fb) { - ret = dm_check_cursor_fb(new_acrtc, new_plane_state, - new_plane_state->fb); - if (ret) - return ret; - } - - return 0; -} - -STATIC_IFN_KUNIT bool dm_should_update_native_cursor(struct drm_atomic_commit *state, - struct drm_crtc *old_plane_crtc, - struct drm_crtc *new_plane_crtc, - bool enable) -{ - struct drm_crtc_state *old_crtc_state, *new_crtc_state; - struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state; - - if (!enable) { - if (old_plane_crtc == NULL) - return true; - - old_crtc_state = drm_atomic_get_old_crtc_state( - state, old_plane_crtc); - dm_old_crtc_state = to_dm_crtc_state(old_crtc_state); - - return dm_old_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE; - } else { - if (new_plane_crtc == NULL) - return true; - - new_crtc_state = drm_atomic_get_new_crtc_state( - state, new_plane_crtc); - dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); - - return dm_new_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE; - } -} -EXPORT_IF_KUNIT(dm_should_update_native_cursor); - static int dm_update_plane_state(struct dc *dc, struct drm_atomic_commit *state, struct drm_plane *plane, @@ -5909,14 +5783,14 @@ static int dm_update_plane_state(struct dc *dc, dm_new_plane_state = to_dm_plane_state(new_plane_state); dm_old_plane_state = to_dm_plane_state(old_plane_state); - update_native_cursor = dm_should_update_native_cursor(state, - old_plane_crtc, - new_plane_crtc, - enable); + update_native_cursor = amdgpu_dm_should_update_native_cursor(state, + old_plane_crtc, + new_plane_crtc, + enable); if (plane->type == DRM_PLANE_TYPE_CURSOR && update_native_cursor) { - ret = dm_check_native_cursor_state(new_plane_crtc, plane, - new_plane_state, enable); + ret = amdgpu_dm_check_native_cursor_state(new_plane_crtc, plane, + new_plane_state, enable); if (ret) return ret; @@ -6046,8 +5920,8 @@ static int dm_update_plane_state(struct dc *dc, out: /* If enabling cursor overlay failed, attempt fallback to native mode */ if (enable && ret == -EINVAL && plane->type == DRM_PLANE_TYPE_CURSOR) { - ret = dm_check_native_cursor_state(new_plane_crtc, plane, - new_plane_state, enable); + ret = amdgpu_dm_check_native_cursor_state(new_plane_crtc, plane, + new_plane_state, enable); if (ret) return ret; @@ -6057,37 +5931,6 @@ out: return ret; } -STATIC_IFN_KUNIT void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, - int *src_w, int *src_h) -{ - switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) { - case DRM_MODE_ROTATE_90: - case DRM_MODE_ROTATE_270: - *src_w = plane_state->src_h >> 16; - *src_h = plane_state->src_w >> 16; - break; - case DRM_MODE_ROTATE_0: - case DRM_MODE_ROTATE_180: - default: - *src_w = plane_state->src_w >> 16; - *src_h = plane_state->src_h >> 16; - break; - } -} -EXPORT_IF_KUNIT(dm_get_oriented_plane_size); - -STATIC_IFN_KUNIT void -dm_get_plane_scale(struct drm_plane_state *plane_state, - int *out_plane_scale_w, int *out_plane_scale_h) -{ - int plane_src_w, plane_src_h; - - dm_get_oriented_plane_size(plane_state, &plane_src_w, &plane_src_h); - *out_plane_scale_w = plane_src_w ? plane_state->crtc_w * 1000 / plane_src_w : 0; - *out_plane_scale_h = plane_src_h ? plane_state->crtc_h * 1000 / plane_src_h : 0; -} -EXPORT_IF_KUNIT(dm_get_plane_scale); - /* * The normalized_zpos value cannot be used by this iterator directly. It's only * calculated for enabled planes, potentially causing normalized_zpos collisions @@ -6095,7 +5938,7 @@ EXPORT_IF_KUNIT(dm_get_plane_scale); * so that the iterator will not generate the same object twice, or loop * indefinitely. */ -static inline struct __drm_planes_state *__get_next_zpos( +struct __drm_planes_state *amdgpu_dm_get_next_zpos( struct drm_atomic_commit *state, struct __drm_planes_state *prev) { @@ -6133,19 +5976,6 @@ static inline struct __drm_planes_state *__get_next_zpos( return &state->planes[highest_i]; } -/* - * Use the uniqueness of the plane's (zpos, drm obj ID) combination to iterate - * by descending zpos, as read from the new plane state. This is the same - * ordering as defined by drm_atomic_normalize_zpos(). - */ -#define for_each_oldnew_plane_in_descending_zpos(__state, plane, old_plane_state, new_plane_state) \ - for (struct __drm_planes_state *__i = __get_next_zpos((__state), NULL); \ - __i != NULL; __i = __get_next_zpos((__state), __i)) \ - for_each_if(((plane) = __i->ptr, \ - (void)(plane) /* Only to avoid unused-but-set-variable warning */, \ - (old_plane_state) = __i->old_state, \ - (new_plane_state) = __i->new_state, 1)) - static int add_affected_mst_dsc_crtcs(struct drm_atomic_commit *state, struct drm_crtc *crtc) { struct drm_connector *connector; @@ -6176,244 +6006,6 @@ static int add_affected_mst_dsc_crtcs(struct drm_atomic_commit *state, struct dr return drm_dp_mst_add_affected_dsc_crtcs(state, &aconnector->mst_root->mst_mgr); } -/** - * DOC: Cursor Modes - Native vs Overlay - * - * In native mode, the cursor uses a integrated cursor pipe within each DCN hw - * plane. It does not require a dedicated hw plane to enable, but it is - * subjected to the same z-order and scaling as the hw plane. It also has format - * restrictions, a RGB cursor in native mode cannot be enabled within a non-RGB - * hw plane. - * - * In overlay mode, the cursor uses a separate DCN hw plane, and thus has its - * own scaling and z-pos. It also has no blending restrictions. It lends to a - * cursor behavior more akin to a DRM client's expectations. However, it does - * occupy an extra DCN plane, and therefore will only be used if a DCN plane is - * available. - */ - -/** - * dm_plane_color_pipeline_active() - Check if a plane's color pipeline active. - * @state: DRM atomic state - * @plane: DRM plane to check - * @use_old: if true, inspect the old colorop states; otherwise the new ones - * - * A color pipeline may be selected (color_pipeline != NULL) but still is - * inactive if every colorop in the chain is bypassed. Only return - * true when at least one colorop has bypass == false, meaning the cursor - * would be subjected to the transformation in native mode. - * - * Return: true if the pipeline modifies pixels, false otherwise. - */ -static bool dm_plane_color_pipeline_active(struct drm_atomic_commit *state, - struct drm_plane *plane, - bool use_old) -{ - struct drm_colorop *colorop; - struct drm_colorop_state *old_colorop_state, *new_colorop_state; - int i; - - for_each_oldnew_colorop_in_state(state, colorop, old_colorop_state, new_colorop_state, i) { - struct drm_colorop_state *cstate = use_old ? old_colorop_state : new_colorop_state; - - if (cstate->colorop->plane != plane) - continue; - if (!cstate->bypass) - return true; - } - return false; -} - -/** - * dm_crtc_get_cursor_mode() - Determine the required cursor mode on crtc - * @adev: amdgpu device - * @state: DRM atomic state - * @dm_crtc_state: amdgpu state for the CRTC containing the cursor - * @cursor_mode: Returns the required cursor mode on dm_crtc_state - * - * Get whether the cursor should be enabled in native mode, or overlay mode, on - * the dm_crtc_state. - * - * The cursor should be enabled in overlay mode if there exists an underlying - * plane - on which the cursor may be blended - that is either YUV formatted, - * scaled differently from the cursor, or has a color pipeline active. - * - * Since zpos info is required, drm_atomic_normalize_zpos must be called before - * calling this function. - * - * Return: 0 on success, or an error code if getting the cursor plane state - * failed. - */ -static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, - struct drm_atomic_commit *state, - struct dm_crtc_state *dm_crtc_state, - enum amdgpu_dm_cursor_mode *cursor_mode) -{ - struct drm_plane_state *old_plane_state, *plane_state, *cursor_state; - struct drm_crtc_state *crtc_state = &dm_crtc_state->base; - struct drm_plane *plane; - bool consider_mode_change = false; - bool entire_crtc_covered = false; - bool cursor_changed = false; - int underlying_scale_w, underlying_scale_h; - int cursor_scale_w, cursor_scale_h; - int i; - - /* Overlay cursor not supported on HW before DCN - * DCN401/420 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions - * as previous DCN generations, so enable native mode on DCN401/420 - * - * Always set native cursor mode when the CRTC is disabled, - * to make sure it doesn't cause atomic commits to fail when - * they are trying to disable the CRTC. - */ - if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) || - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) || - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) || - !dm_crtc_state->base.enable) { - *cursor_mode = DM_CURSOR_NATIVE_MODE; - return 0; - } - - /* Init cursor_mode to be the same as current */ - *cursor_mode = dm_crtc_state->cursor_mode; - - /* - * Cursor mode can change if a plane's format changes, scale changes, is - * enabled/disabled, z-order changes, or color management properties change. - */ - for_each_oldnew_plane_in_state(state, plane, old_plane_state, plane_state, i) { - int new_scale_w, new_scale_h, old_scale_w, old_scale_h; - - /* Only care about planes on this CRTC */ - if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0) - continue; - - if (plane->type == DRM_PLANE_TYPE_CURSOR) - cursor_changed = true; - - if (drm_atomic_plane_enabling(old_plane_state, plane_state) || - drm_atomic_plane_disabling(old_plane_state, plane_state) || - old_plane_state->fb->format != plane_state->fb->format) { - consider_mode_change = true; - break; - } - - dm_get_plane_scale(plane_state, &new_scale_w, &new_scale_h); - dm_get_plane_scale(old_plane_state, &old_scale_w, &old_scale_h); - if (new_scale_w != old_scale_w || new_scale_h != old_scale_h) { - consider_mode_change = true; - break; - } - - /* - * A non-cursor plane moving or resizing (without a scale change) - * changes how much of the CRTC it covers. This can create or - * remove a hole under the cursor and thus flip the required - * cursor mode (native vs overlay), so its destination rect must - * be re-evaluated too. - * - * The cursor plane itself is deliberately excluded: the cursor - * mode depends on the underlying planes' coverage, not on the - * cursor's position (see the entire_crtc_covered logic below). - * Triggering on cursor movement would force every legacy cursor - * update off its fast path, and in a cursor-only commit - where - * the underlying planes are not part of the state - the coverage - * loop would see no covering plane and misevaluate the mode as - * overlay, regressing flip-vs-cursor-legacy. - */ - if (plane->type != DRM_PLANE_TYPE_CURSOR && - (old_plane_state->crtc_x != plane_state->crtc_x || - old_plane_state->crtc_y != plane_state->crtc_y || - old_plane_state->crtc_w != plane_state->crtc_w || - old_plane_state->crtc_h != plane_state->crtc_h)) { - consider_mode_change = true; - break; - } - - if (dm_plane_color_pipeline_active(state, plane, true) != - dm_plane_color_pipeline_active(state, plane, false)) { - consider_mode_change = true; - break; - } - } - - if (!consider_mode_change && !crtc_state->zpos_changed) - return 0; - - /* - * If no cursor change on this CRTC, and not enabled on this CRTC, then - * no need to set cursor mode. This avoids needlessly locking the cursor - * state. - */ - if (!cursor_changed && - !(drm_plane_mask(crtc_state->crtc->cursor) & crtc_state->plane_mask)) { - return 0; - } - - cursor_state = drm_atomic_get_plane_state(state, - crtc_state->crtc->cursor); - if (IS_ERR(cursor_state)) - return PTR_ERR(cursor_state); - - /* Cursor is disabled */ - if (!cursor_state->fb) - return 0; - - /* For all planes in descending z-order (all of which are below cursor - * as per zpos definitions), check their scaling and format - */ - for_each_oldnew_plane_in_descending_zpos(state, plane, old_plane_state, plane_state) { - - /* Only care about non-cursor planes on this CRTC */ - if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0 || - plane->type == DRM_PLANE_TYPE_CURSOR) - continue; - - /* Underlying plane is YUV format - use overlay cursor */ - if (amdgpu_dm_plane_is_video_format(plane_state->fb->format->format)) { - *cursor_mode = DM_CURSOR_OVERLAY_MODE; - return 0; - } - - /* Underlying plane has an active color pipeline - cursor would be transformed */ - if (dm_plane_color_pipeline_active(state, plane, false)) { - *cursor_mode = DM_CURSOR_OVERLAY_MODE; - return 0; - } - - dm_get_plane_scale(plane_state, - &underlying_scale_w, &underlying_scale_h); - dm_get_plane_scale(cursor_state, - &cursor_scale_w, &cursor_scale_h); - - /* Underlying plane has different scale - use overlay cursor */ - if (cursor_scale_w != underlying_scale_w && - cursor_scale_h != underlying_scale_h) { - *cursor_mode = DM_CURSOR_OVERLAY_MODE; - return 0; - } - - /* If this plane covers the whole CRTC, no need to check planes underneath */ - if (plane_state->crtc_x <= 0 && plane_state->crtc_y <= 0 && - plane_state->crtc_x + plane_state->crtc_w >= crtc_state->mode.hdisplay && - plane_state->crtc_y + plane_state->crtc_h >= crtc_state->mode.vdisplay) { - entire_crtc_covered = true; - break; - } - } - - /* If planes do not cover the entire CRTC, use overlay mode to enable - * cursor over holes - */ - if (entire_crtc_covered) - *cursor_mode = DM_CURSOR_NATIVE_MODE; - else - *cursor_mode = DM_CURSOR_OVERLAY_MODE; - - return 0; -} - static bool amdgpu_dm_crtc_mem_type_changed(struct drm_device *dev, struct drm_atomic_commit *state, struct drm_crtc_state *crtc_state) @@ -6617,8 +6209,8 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); - ret = dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state, - &dm_new_crtc_state->cursor_mode); + ret = amdgpu_dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state, + &dm_new_crtc_state->cursor_mode); if (ret) { drm_dbg(dev, "Failed to determine cursor mode: %pe\n", ERR_PTR(ret)); goto fail; @@ -6752,8 +6344,8 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, } /* If HW can only do native cursor, check restrictions again */ - ret = dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state, - &required_cursor_mode); + ret = amdgpu_dm_crtc_get_cursor_mode(adev, state, dm_new_crtc_state, + &required_cursor_mode); if (ret) { drm_dbg_driver(crtc->dev, "[CRTC:%d:%s] Checking cursor mode failed\n", diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 1ef92a28e3e9..1ddf5bc88a03 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1141,6 +1141,22 @@ void amdgpu_dm_emulated_link_detect(struct dc_link *link); void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, struct dc_sink *sink); +struct __drm_planes_state *amdgpu_dm_get_next_zpos(struct drm_atomic_commit *state, + struct __drm_planes_state *prev); + +/* + * Use the uniqueness of the plane's (zpos, drm obj ID) combination to iterate + * by descending zpos, as read from the new plane state. This is the same + * ordering as defined by drm_atomic_normalize_zpos(). + */ +#define for_each_oldnew_plane_in_descending_zpos(__state, plane, old_plane_state, new_plane_state) \ + for (struct __drm_planes_state *__i = amdgpu_dm_get_next_zpos((__state), NULL); \ + __i != NULL; __i = amdgpu_dm_get_next_zpos((__state), __i)) \ + for_each_if(((plane) = __i->ptr, \ + (void)(plane) /* Only to avoid unused-but-set-variable warning */, \ + (old_plane_state) = __i->old_state, \ + (new_plane_state) = __i->new_state, 1)) + #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) struct amdgpu_ip_block; bool dm_is_idle(struct amdgpu_ip_block *ip_block); @@ -1157,19 +1173,11 @@ int dm_crtc_get_scanoutpos(struct amdgpu_device *adev, int crtc, struct dm_atomic_state *dm_atomic_get_new_state(struct drm_atomic_commit *state); void dm_atomic_destroy_state(struct drm_private_obj *obj, struct drm_private_state *state); -bool dm_should_update_native_cursor(struct drm_atomic_commit *state, - struct drm_crtc *old_plane_crtc, - struct drm_crtc *new_plane_crtc, - bool enable); int dm_plane_layer_index_cmp(const void *a, const void *b); int fill_plane_color_attributes(const struct drm_plane_state *plane_state, const enum surface_pixel_format format, enum dc_color_space *color_space); bool modereset_required(struct drm_crtc_state *crtc_state); -void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, - int *src_w, int *src_h); -void dm_get_plane_scale(struct drm_plane_state *plane_state, - int *out_plane_scale_w, int *out_plane_scale_h); bool is_scaling_state_different(const struct dm_connector_state *dm_state, const struct dm_connector_state *old_dm_state); void set_multisync_trigger_params(struct dc_stream_state *stream); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c new file mode 100644 index 000000000000..ab50d58896ab --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dc.h" +#include "dal_asic_id.h" +#include "amdgpu.h" +#include "amdgpu_display.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_plane.h" +#include "amdgpu_dm_cursor.h" +#include "dm_helpers.h" + +static int dm_check_cursor_fb(struct amdgpu_crtc *new_acrtc, + struct drm_plane_state *new_plane_state, + struct drm_framebuffer *fb) +{ + struct amdgpu_device *adev = drm_to_adev(new_acrtc->base.dev); + struct amdgpu_framebuffer *afb = to_amdgpu_framebuffer(fb); + unsigned int pitch; + bool linear; + + if (fb->width > new_acrtc->max_cursor_width || + fb->height > new_acrtc->max_cursor_height) { + drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB size %dx%d\n", + new_plane_state->fb->width, + new_plane_state->fb->height); + return -EINVAL; + } + if (new_plane_state->src_w != fb->width << 16 || + new_plane_state->src_h != fb->height << 16) { + drm_dbg_atomic(adev_to_drm(adev), "Cropping not supported for cursor plane\n"); + return -EINVAL; + } + + /* Pitch in pixels */ + pitch = fb->pitches[0] / fb->format->cpp[0]; + + if (fb->width != pitch) { + drm_dbg_atomic(adev_to_drm(adev), "Cursor FB width %d doesn't match pitch %d", + fb->width, pitch); + return -EINVAL; + } + + switch (pitch) { + case 64: + case 128: + case 256: + /* FB pitch is supported by cursor plane */ + break; + default: + drm_dbg_atomic(adev_to_drm(adev), "Bad cursor FB pitch %d px\n", pitch); + return -EINVAL; + } + + /* Core DRM takes care of checking FB modifiers, so we only need to + * check tiling flags when the FB doesn't have a modifier. + */ + if (!(fb->flags & DRM_MODE_FB_MODIFIERS)) { +#if defined(CONFIG_DRM_AMD_DC_DCN6_0) || defined(CONFIG_DRM_AMD_DC_DCN5_0) + if (adev->family == AMDGPU_FAMILY_GC_12_0_0 + || adev->family == AMDGPU_FAMILY_GC_13_0_1) { +#else + if (adev->family == AMDGPU_FAMILY_GC_12_0_0) { +#endif + linear = AMDGPU_TILING_GET(afb->tiling_flags, GFX12_SWIZZLE_MODE) == 0; + } else if (adev->family >= AMDGPU_FAMILY_AI) { + linear = AMDGPU_TILING_GET(afb->tiling_flags, SWIZZLE_MODE) == 0; + } else { + linear = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_2D_TILED_THIN1 && + AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) != DC_ARRAY_1D_TILED_THIN1 && + AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE) == 0; + } + if (!linear) { + drm_dbg_atomic(adev_to_drm(adev), "Cursor FB not linear"); + return -EINVAL; + } + } + + return 0; +} + +/* + * Helper function for checking the cursor in native mode + */ +int amdgpu_dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc, + struct drm_plane *plane, + struct drm_plane_state *new_plane_state, + bool enable) +{ + + struct amdgpu_crtc *new_acrtc; + int ret; + + if (!enable || !new_plane_crtc || + drm_atomic_plane_disabling(plane->state, new_plane_state)) + return 0; + + new_acrtc = to_amdgpu_crtc(new_plane_crtc); + + if (new_plane_state->src_x != 0 || new_plane_state->src_y != 0) { + drm_dbg_atomic(new_plane_crtc->dev, "Cropping not supported for cursor plane\n"); + return -EINVAL; + } + + if (new_plane_state->fb) { + ret = dm_check_cursor_fb(new_acrtc, new_plane_state, + new_plane_state->fb); + if (ret) + return ret; + } + + return 0; +} + +bool amdgpu_dm_should_update_native_cursor(struct drm_atomic_commit *state, + struct drm_crtc *old_plane_crtc, + struct drm_crtc *new_plane_crtc, + bool enable) +{ + struct drm_crtc_state *old_crtc_state, *new_crtc_state; + struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state; + + if (!enable) { + if (old_plane_crtc == NULL) + return true; + + old_crtc_state = drm_atomic_get_old_crtc_state( + state, old_plane_crtc); + dm_old_crtc_state = to_dm_crtc_state(old_crtc_state); + + return dm_old_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE; + } + + if (new_plane_crtc == NULL) + return true; + + new_crtc_state = drm_atomic_get_new_crtc_state( + state, new_plane_crtc); + dm_new_crtc_state = to_dm_crtc_state(new_crtc_state); + + return dm_new_crtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE; +} +EXPORT_IF_KUNIT(amdgpu_dm_should_update_native_cursor); + +STATIC_IFN_KUNIT void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, + int *src_w, int *src_h) +{ + switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) { + case DRM_MODE_ROTATE_90: + case DRM_MODE_ROTATE_270: + *src_w = plane_state->src_h >> 16; + *src_h = plane_state->src_w >> 16; + break; + case DRM_MODE_ROTATE_0: + case DRM_MODE_ROTATE_180: + default: + *src_w = plane_state->src_w >> 16; + *src_h = plane_state->src_h >> 16; + break; + } +} +EXPORT_IF_KUNIT(dm_get_oriented_plane_size); + +STATIC_IFN_KUNIT void +dm_get_plane_scale(struct drm_plane_state *plane_state, + int *out_plane_scale_w, int *out_plane_scale_h) +{ + int plane_src_w, plane_src_h; + + dm_get_oriented_plane_size(plane_state, &plane_src_w, &plane_src_h); + *out_plane_scale_w = plane_src_w ? plane_state->crtc_w * 1000 / plane_src_w : 0; + *out_plane_scale_h = plane_src_h ? plane_state->crtc_h * 1000 / plane_src_h : 0; +} +EXPORT_IF_KUNIT(dm_get_plane_scale); + +/** + * DOC: Cursor Modes - Native vs Overlay + * + * In native mode, the cursor uses a integrated cursor pipe within each DCN hw + * plane. It does not require a dedicated hw plane to enable, but it is + * subjected to the same z-order and scaling as the hw plane. It also has format + * restrictions, a RGB cursor in native mode cannot be enabled within a non-RGB + * hw plane. + * + * In overlay mode, the cursor uses a separate DCN hw plane, and thus has its + * own scaling and z-pos. It also has no blending restrictions. It lends to a + * cursor behavior more akin to a DRM client's expectations. However, it does + * occupy an extra DCN plane, and therefore will only be used if a DCN plane is + * available. + */ + +/** + * dm_plane_color_pipeline_active() - Check if a plane's color pipeline active. + * @state: DRM atomic state + * @plane: DRM plane to check + * @use_old: if true, inspect the old colorop states; otherwise the new ones + * + * A color pipeline may be selected (color_pipeline != NULL) but still is + * inactive if every colorop in the chain is bypassed. Only return + * true when at least one colorop has bypass == false, meaning the cursor + * would be subjected to the transformation in native mode. + * + * Return: true if the pipeline modifies pixels, false otherwise. + */ +static bool dm_plane_color_pipeline_active(struct drm_atomic_commit *state, + struct drm_plane *plane, + bool use_old) +{ + struct drm_colorop *colorop; + struct drm_colorop_state *old_colorop_state, *new_colorop_state; + int i; + + for_each_oldnew_colorop_in_state(state, colorop, old_colorop_state, new_colorop_state, i) { + struct drm_colorop_state *cstate = use_old ? old_colorop_state : new_colorop_state; + + if (cstate->colorop->plane != plane) + continue; + if (!cstate->bypass) + return true; + } + return false; +} + +/** + * amdgpu_dm_crtc_get_cursor_mode() - Determine the required cursor mode on crtc + * @adev: amdgpu device + * @state: DRM atomic state + * @dm_crtc_state: amdgpu state for the CRTC containing the cursor + * @cursor_mode: Returns the required cursor mode on dm_crtc_state + * + * Get whether the cursor should be enabled in native mode, or overlay mode, on + * the dm_crtc_state. + * + * The cursor should be enabled in overlay mode if there exists an underlying + * plane - on which the cursor may be blended - that is either YUV formatted, + * scaled differently from the cursor, or has a color pipeline active. + * + * Since zpos info is required, drm_atomic_normalize_zpos must be called before + * calling this function. + * + * Return: 0 on success, or an error code if getting the cursor plane state + * failed. + */ +int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev, + struct drm_atomic_commit *state, + struct dm_crtc_state *dm_crtc_state, + enum amdgpu_dm_cursor_mode *cursor_mode) +{ + struct drm_plane_state *old_plane_state, *plane_state, *cursor_state; + struct drm_crtc_state *crtc_state = &dm_crtc_state->base; + struct drm_plane *plane; + bool consider_mode_change = false; + bool entire_crtc_covered = false; + bool cursor_changed = false; + int underlying_scale_w, underlying_scale_h; + int cursor_scale_w, cursor_scale_h; + int i; + + /* Overlay cursor not supported on HW before DCN + * DCN401/420 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions + * as previous DCN generations, so enable native mode on DCN401/420 + */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) || + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) || +#if defined(CONFIG_DRM_AMD_DC_DCN6_0) + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) || + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(6, 0, 0)) { +#else + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1)) { +#endif + *cursor_mode = DM_CURSOR_NATIVE_MODE; + return 0; + } + + /* Init cursor_mode to be the same as current */ + *cursor_mode = dm_crtc_state->cursor_mode; + + /* + * Cursor mode can change if a plane's format changes, scale changes, is + * enabled/disabled, z-order changes, or color management properties change. + */ + for_each_oldnew_plane_in_state(state, plane, old_plane_state, plane_state, i) { + int new_scale_w, new_scale_h, old_scale_w, old_scale_h; + + /* Only care about planes on this CRTC */ + if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0) + continue; + + if (plane->type == DRM_PLANE_TYPE_CURSOR) + cursor_changed = true; + + if (drm_atomic_plane_enabling(old_plane_state, plane_state) || + drm_atomic_plane_disabling(old_plane_state, plane_state) || + old_plane_state->fb->format != plane_state->fb->format) { + consider_mode_change = true; + break; + } + + dm_get_plane_scale(plane_state, &new_scale_w, &new_scale_h); + dm_get_plane_scale(old_plane_state, &old_scale_w, &old_scale_h); + if (new_scale_w != old_scale_w || new_scale_h != old_scale_h) { + consider_mode_change = true; + break; + } + + /* + * A non-cursor plane moving or resizing (without a scale change) + * changes how much of the CRTC it covers. This can create or + * remove a hole under the cursor and thus flip the required + * cursor mode (native vs overlay), so its destination rect must + * be re-evaluated too. + * + * The cursor plane itself is deliberately excluded: the cursor + * mode depends on the underlying planes' coverage, not on the + * cursor's position (see the entire_crtc_covered logic below). + * Triggering on cursor movement would force every legacy cursor + * update off its fast path, and in a cursor-only commit - where + * the underlying planes are not part of the state - the coverage + * loop would see no covering plane and misevaluate the mode as + * overlay, regressing flip-vs-cursor-legacy. + */ + if (plane->type != DRM_PLANE_TYPE_CURSOR && + (old_plane_state->crtc_x != plane_state->crtc_x || + old_plane_state->crtc_y != plane_state->crtc_y || + old_plane_state->crtc_w != plane_state->crtc_w || + old_plane_state->crtc_h != plane_state->crtc_h)) { + consider_mode_change = true; + break; + } + + if (dm_plane_color_pipeline_active(state, plane, true) != + dm_plane_color_pipeline_active(state, plane, false)) { + consider_mode_change = true; + break; + } + } + + if (!consider_mode_change && !crtc_state->zpos_changed) + return 0; + + /* + * If no cursor change on this CRTC, and not enabled on this CRTC, then + * no need to set cursor mode. This avoids needlessly locking the cursor + * state. + */ + if (!cursor_changed && + !(drm_plane_mask(crtc_state->crtc->cursor) & crtc_state->plane_mask)) { + return 0; + } + + cursor_state = drm_atomic_get_plane_state(state, + crtc_state->crtc->cursor); + if (IS_ERR(cursor_state)) + return PTR_ERR(cursor_state); + + /* Cursor is disabled */ + if (!cursor_state->fb) + return 0; + + /* For all planes in descending z-order (all of which are below cursor + * as per zpos definitions), check their scaling and format + */ + for_each_oldnew_plane_in_descending_zpos(state, plane, old_plane_state, plane_state) { + + /* Only care about non-cursor planes on this CRTC */ + if ((drm_plane_mask(plane) & crtc_state->plane_mask) == 0 || + plane->type == DRM_PLANE_TYPE_CURSOR) + continue; + + /* Underlying plane is YUV format - use overlay cursor */ + if (amdgpu_dm_plane_is_video_format(plane_state->fb->format->format)) { + *cursor_mode = DM_CURSOR_OVERLAY_MODE; + return 0; + } + + /* Underlying plane has an active color pipeline - cursor would be transformed */ + if (dm_plane_color_pipeline_active(state, plane, false)) { + *cursor_mode = DM_CURSOR_OVERLAY_MODE; + return 0; + } + + dm_get_plane_scale(plane_state, + &underlying_scale_w, &underlying_scale_h); + dm_get_plane_scale(cursor_state, + &cursor_scale_w, &cursor_scale_h); + + /* Underlying plane has different scale - use overlay cursor */ + if (cursor_scale_w != underlying_scale_w && + cursor_scale_h != underlying_scale_h) { + *cursor_mode = DM_CURSOR_OVERLAY_MODE; + return 0; + } + + /* If this plane covers the whole CRTC, no need to check planes underneath */ + if (plane_state->crtc_x <= 0 && plane_state->crtc_y <= 0 && + plane_state->crtc_x + plane_state->crtc_w >= crtc_state->mode.hdisplay && + plane_state->crtc_y + plane_state->crtc_h >= crtc_state->mode.vdisplay) { + entire_crtc_covered = true; + break; + } + } + + /* If planes do not cover the entire CRTC, use overlay mode to enable + * cursor over holes + */ + if (entire_crtc_covered) + *cursor_mode = DM_CURSOR_NATIVE_MODE; + else + *cursor_mode = DM_CURSOR_OVERLAY_MODE; + + return 0; +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h new file mode 100644 index 000000000000..5f619e4474aa --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_cursor.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#ifndef __AMDGPU_DM_CURSOR_H__ +#define __AMDGPU_DM_CURSOR_H__ + +int amdgpu_dm_check_native_cursor_state(struct drm_crtc *new_plane_crtc, + struct drm_plane *plane, + struct drm_plane_state *new_plane_state, + bool enable); + +bool amdgpu_dm_should_update_native_cursor(struct drm_atomic_commit *state, + struct drm_crtc *old_plane_crtc, + struct drm_crtc *new_plane_crtc, + bool enable); + +int amdgpu_dm_crtc_get_cursor_mode(struct amdgpu_device *adev, + struct drm_atomic_commit *state, + struct dm_crtc_state *dm_crtc_state, + enum amdgpu_dm_cursor_mode *cursor_mode); + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, + int *src_w, int *src_h); +void dm_get_plane_scale(struct drm_plane_state *plane_state, + int *out_plane_scale_w, int *out_plane_scale_h); +#endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */ + +#endif /* __AMDGPU_DM_CURSOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index e58ffc4dcd84..3a51fccc2a5e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_freesync_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_cursor_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c new file mode 100644 index 000000000000..3da76794ece9 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_cursor_test.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_cursor.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_cursor.h" + +/* Tests for amdgpu_dm_should_update_native_cursor() */ + +/** + * dm_test_should_update_native_cursor_without_crtc - Test NULL crtc cases update native cursor + * @test: The KUnit test context + */ +static void dm_test_should_update_native_cursor_without_crtc(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_update_native_cursor(NULL, NULL, NULL, false)); + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_update_native_cursor(NULL, NULL, NULL, true)); +} + +/** + * dm_test_should_update_native_cursor_disable_native - Test disable path reads old crtc cursor mode + * @test: The KUnit test context + */ +static void dm_test_should_update_native_cursor_disable_native(struct kunit *test) +{ + struct dm_crtc_state *dm_crtc_state; + struct drm_atomic_commit *state; + struct drm_crtc *crtc; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->crtcs); + + crtc->index = 0; + dm_crtc_state->cursor_mode = DM_CURSOR_NATIVE_MODE; + state->crtcs[0].old_state = &dm_crtc_state->base; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_should_update_native_cursor(state, crtc, NULL, false)); +} + +/** + * dm_test_should_update_native_cursor_enable_overlay - Test enable path reads new crtc cursor mode + * @test: The KUnit test context + */ +static void dm_test_should_update_native_cursor_enable_overlay(struct kunit *test) +{ + struct dm_crtc_state *dm_crtc_state; + struct drm_atomic_commit *state; + struct drm_crtc *crtc; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->crtcs); + + crtc->index = 0; + dm_crtc_state->cursor_mode = DM_CURSOR_OVERLAY_MODE; + state->crtcs[0].new_state = &dm_crtc_state->base; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_should_update_native_cursor(state, NULL, crtc, true)); +} + +/* Tests for dm_get_oriented_plane_size() */ + +/** + * dm_test_oriented_plane_size_rotate_0 - Test Oriented plane size rotate 0 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_0(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_0; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1920); + KUNIT_EXPECT_EQ(test, src_h, 1080); +} + +/** + * dm_test_oriented_plane_size_rotate_90 - Test Oriented plane size rotate 90 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_90(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_90; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1080); + KUNIT_EXPECT_EQ(test, src_h, 1920); +} + +/** + * dm_test_oriented_plane_size_rotate_180 - Test Oriented plane size rotate 180 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_180(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_180; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1920); + KUNIT_EXPECT_EQ(test, src_h, 1080); +} + +/** + * dm_test_oriented_plane_size_rotate_270 - Test Oriented plane size rotate 270 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_270(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_270; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1080); + KUNIT_EXPECT_EQ(test, src_h, 1920); +} + +/* Tests for dm_get_plane_scale() */ + +/** + * dm_test_get_plane_scale_identity - Test Get plane scale identity + * @test: The KUnit test context + */ +static void dm_test_get_plane_scale_identity(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int scale_w = 0; + int scale_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_0; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + plane_state.crtc_w = 1920; + plane_state.crtc_h = 1080; + + dm_get_plane_scale(&plane_state, &scale_w, &scale_h); + + KUNIT_EXPECT_EQ(test, scale_w, 1000); + KUNIT_EXPECT_EQ(test, scale_h, 1000); +} + +/** + * dm_test_get_plane_scale_rotate_90_identity - Test Get plane scale rotate 90 identity + * @test: The KUnit test context + */ +static void dm_test_get_plane_scale_rotate_90_identity(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int scale_w = 0; + int scale_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_90; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + plane_state.crtc_w = 1080; + plane_state.crtc_h = 1920; + + dm_get_plane_scale(&plane_state, &scale_w, &scale_h); + + KUNIT_EXPECT_EQ(test, scale_w, 1000); + KUNIT_EXPECT_EQ(test, scale_h, 1000); +} + +/** + * dm_test_get_plane_scale_zero_src_width - Test Get plane scale zero src width + * @test: The KUnit test context + */ +static void dm_test_get_plane_scale_zero_src_width(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int scale_w = 0; + int scale_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_0; + plane_state.src_w = 0; + plane_state.src_h = 1080 << 16; + plane_state.crtc_w = 100; + plane_state.crtc_h = 200; + + dm_get_plane_scale(&plane_state, &scale_w, &scale_h); + + KUNIT_EXPECT_EQ(test, scale_w, 0); + KUNIT_EXPECT_EQ(test, scale_h, 185); +} + +static struct kunit_case amdgpu_dm_cursor_tests[] = { + /* amdgpu_dm_should_update_native_cursor */ + KUNIT_CASE(dm_test_should_update_native_cursor_without_crtc), + KUNIT_CASE(dm_test_should_update_native_cursor_disable_native), + KUNIT_CASE(dm_test_should_update_native_cursor_enable_overlay), + /* dm_get_oriented_plane_size */ + KUNIT_CASE(dm_test_oriented_plane_size_rotate_0), + KUNIT_CASE(dm_test_oriented_plane_size_rotate_90), + KUNIT_CASE(dm_test_oriented_plane_size_rotate_180), + KUNIT_CASE(dm_test_oriented_plane_size_rotate_270), + /* dm_get_plane_scale */ + KUNIT_CASE(dm_test_get_plane_scale_identity), + KUNIT_CASE(dm_test_get_plane_scale_rotate_90_identity), + KUNIT_CASE(dm_test_get_plane_scale_zero_src_width), + {} +}; + +static struct kunit_suite amdgpu_dm_cursor_test_suite = { + .name = "amdgpu_dm_cursor", + .test_cases = amdgpu_dm_cursor_tests, +}; + +kunit_test_suite(amdgpu_dm_cursor_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_cursor"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 428d24259198..3c10eec9b1e0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -227,76 +227,6 @@ static void dm_test_atomic_get_new_state_match(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, dm_atomic_get_new_state(state), dm_state); } -/** - * dm_test_should_update_native_cursor_without_crtc - Test NULL crtc cases update native cursor - * @test: The KUnit test context - */ -static void dm_test_should_update_native_cursor_without_crtc(struct kunit *test) -{ - KUNIT_EXPECT_TRUE(test, dm_should_update_native_cursor(NULL, NULL, NULL, false)); - KUNIT_EXPECT_TRUE(test, dm_should_update_native_cursor(NULL, NULL, NULL, true)); -} - -/** - * dm_test_should_update_native_cursor_disable_native - Test disable path reads old crtc cursor mode - * @test: The KUnit test context - */ -static void dm_test_should_update_native_cursor_disable_native(struct kunit *test) -{ - struct dm_crtc_state *dm_crtc_state; - struct drm_atomic_commit *state; - struct drm_crtc *crtc; - - state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, state); - - crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, crtc); - - dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); - - state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, state->crtcs); - - crtc->index = 0; - dm_crtc_state->cursor_mode = DM_CURSOR_NATIVE_MODE; - state->crtcs[0].old_state = &dm_crtc_state->base; - - KUNIT_EXPECT_TRUE(test, - dm_should_update_native_cursor(state, crtc, NULL, false)); -} - -/** - * dm_test_should_update_native_cursor_enable_overlay - Test enable path reads new crtc cursor mode - * @test: The KUnit test context - */ -static void dm_test_should_update_native_cursor_enable_overlay(struct kunit *test) -{ - struct dm_crtc_state *dm_crtc_state; - struct drm_atomic_commit *state; - struct drm_crtc *crtc; - - state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, state); - - crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, crtc); - - dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); - - state->crtcs = kunit_kzalloc(test, sizeof(*state->crtcs), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, state->crtcs); - - crtc->index = 0; - dm_crtc_state->cursor_mode = DM_CURSOR_OVERLAY_MODE; - state->crtcs[0].new_state = &dm_crtc_state->base; - - KUNIT_EXPECT_FALSE(test, - dm_should_update_native_cursor(state, NULL, crtc, true)); -} - /** * dm_test_atomic_destroy_state_no_context - Test destroying DM atomic state without a DC context * @test: The KUnit test context @@ -599,156 +529,6 @@ static void dm_test_modereset_not_required_when_inactive_without_modeset(struct KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state)); } -/* Tests for dm_get_oriented_plane_size() */ - -/** - * dm_test_oriented_plane_size_rotate_0 - Test Oriented plane size rotate 0 - * @test: The KUnit test context - */ -static void dm_test_oriented_plane_size_rotate_0(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int src_w = 0; - int src_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_0; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); - - KUNIT_EXPECT_EQ(test, src_w, 1920); - KUNIT_EXPECT_EQ(test, src_h, 1080); -} - -/** - * dm_test_oriented_plane_size_rotate_90 - Test Oriented plane size rotate 90 - * @test: The KUnit test context - */ -static void dm_test_oriented_plane_size_rotate_90(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int src_w = 0; - int src_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_90; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); - - KUNIT_EXPECT_EQ(test, src_w, 1080); - KUNIT_EXPECT_EQ(test, src_h, 1920); -} - -/** - * dm_test_oriented_plane_size_rotate_180 - Test Oriented plane size rotate 180 - * @test: The KUnit test context - */ -static void dm_test_oriented_plane_size_rotate_180(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int src_w = 0; - int src_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_180; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); - - KUNIT_EXPECT_EQ(test, src_w, 1920); - KUNIT_EXPECT_EQ(test, src_h, 1080); -} - -/** - * dm_test_oriented_plane_size_rotate_270 - Test Oriented plane size rotate 270 - * @test: The KUnit test context - */ -static void dm_test_oriented_plane_size_rotate_270(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int src_w = 0; - int src_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_270; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - - dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); - - KUNIT_EXPECT_EQ(test, src_w, 1080); - KUNIT_EXPECT_EQ(test, src_h, 1920); -} - -/* Tests for dm_get_plane_scale() */ - -/** - * dm_test_get_plane_scale_identity - Test Get plane scale identity - * @test: The KUnit test context - */ -static void dm_test_get_plane_scale_identity(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int scale_w = 0; - int scale_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_0; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - plane_state.crtc_w = 1920; - plane_state.crtc_h = 1080; - - dm_get_plane_scale(&plane_state, &scale_w, &scale_h); - - KUNIT_EXPECT_EQ(test, scale_w, 1000); - KUNIT_EXPECT_EQ(test, scale_h, 1000); -} - -/** - * dm_test_get_plane_scale_rotate_90_identity - Test Get plane scale rotate 90 identity - * @test: The KUnit test context - */ -static void dm_test_get_plane_scale_rotate_90_identity(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int scale_w = 0; - int scale_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_90; - plane_state.src_w = 1920 << 16; - plane_state.src_h = 1080 << 16; - plane_state.crtc_w = 1080; - plane_state.crtc_h = 1920; - - dm_get_plane_scale(&plane_state, &scale_w, &scale_h); - - KUNIT_EXPECT_EQ(test, scale_w, 1000); - KUNIT_EXPECT_EQ(test, scale_h, 1000); -} - -/** - * dm_test_get_plane_scale_zero_src_width - Test Get plane scale zero src width - * @test: The KUnit test context - */ -static void dm_test_get_plane_scale_zero_src_width(struct kunit *test) -{ - struct drm_plane_state plane_state = { 0 }; - int scale_w = 0; - int scale_h = 0; - - plane_state.rotation = DRM_MODE_ROTATE_0; - plane_state.src_w = 0; - plane_state.src_h = 1080 << 16; - plane_state.crtc_w = 100; - plane_state.crtc_h = 200; - - dm_get_plane_scale(&plane_state, &scale_w, &scale_h); - - KUNIT_EXPECT_EQ(test, scale_w, 0); - KUNIT_EXPECT_EQ(test, scale_h, 185); -} - /* Tests for is_scaling_state_different() */ /** @@ -1386,9 +1166,6 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_crtc_get_scanoutpos_no_stream), KUNIT_CASE(dm_test_atomic_get_new_state_empty), KUNIT_CASE(dm_test_atomic_get_new_state_match), - KUNIT_CASE(dm_test_should_update_native_cursor_without_crtc), - KUNIT_CASE(dm_test_should_update_native_cursor_disable_native), - KUNIT_CASE(dm_test_should_update_native_cursor_enable_overlay), KUNIT_CASE(dm_test_atomic_destroy_state_no_context), /* dm_plane_layer_index_cmp */ KUNIT_CASE(dm_test_plane_layer_index_cmp_equal), @@ -1407,15 +1184,6 @@ static struct kunit_case amdgpu_dm_tests[] = { KUNIT_CASE(dm_test_modereset_required_when_inactive_and_modeset), KUNIT_CASE(dm_test_modereset_not_required_when_active_and_modeset), KUNIT_CASE(dm_test_modereset_not_required_when_inactive_without_modeset), - /* dm_get_oriented_plane_size */ - KUNIT_CASE(dm_test_oriented_plane_size_rotate_0), - KUNIT_CASE(dm_test_oriented_plane_size_rotate_90), - KUNIT_CASE(dm_test_oriented_plane_size_rotate_180), - KUNIT_CASE(dm_test_oriented_plane_size_rotate_270), - /* dm_get_plane_scale */ - KUNIT_CASE(dm_test_get_plane_scale_identity), - KUNIT_CASE(dm_test_get_plane_scale_rotate_90_identity), - KUNIT_CASE(dm_test_get_plane_scale_zero_src_width), /* is_scaling_state_different */ KUNIT_CASE(dm_test_scaling_state_same), KUNIT_CASE(dm_test_scaling_state_scaling_changed), -- cgit v1.2.3 From ff1df0051cb922f6d71a3b6d81130057a2319515 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 13 Jul 2026 14:33:30 -0600 Subject: drm/amd/display: add KUnit tests for audio component get_eld [WHAT] Add KUnit coverage for amdgpu_dm_audio_component_get_eld(), one of the functions reported as untested by gcov. The tests use the standard DRM KUnit device plus drm_connector_init() to build a real connector list, then invoke the bound audio component get_eld callback. They cover the matching-connector ELD copy path, the writeback-connector skip, and the no-match (disabled) path. A shared connector funcs table and cleanup action back the fixtures. Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_audio_test.c | 119 +++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c index 3f0108e9a951..d30b7f644f97 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c @@ -8,7 +8,12 @@ #include #include +#include #include +#include +#include +#include +#include #include "dc.h" #include "dc/inc/core_types.h" @@ -17,6 +22,20 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_audio.h" +#include "amdgpu_dm_kunit_test_helpers.h" + +static const struct drm_connector_funcs dm_test_audio_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, +}; + +static void dm_test_audio_connector_cleanup(void *data) +{ + drm_connector_cleanup(data); +} /* Tests for amdgpu_dm_audio_init() */ @@ -569,6 +588,103 @@ static void dm_test_audio_init_pins_zero_count(struct kunit *test) /* End of tests for amdgpu_dm_audio_init_pins() */ +/* Tests for amdgpu_dm_audio_component_get_eld() */ + +/** + * dm_test_audio_component_get_eld_copies_matching_connector - Test ELD lookup + * @test: The KUnit test context + * + * The bound component callback should find the connector whose audio instance + * matches the requested port, report it enabled, and copy the connector ELD. + */ +static void dm_test_audio_component_get_eld_copies_matching_connector(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_dm_connector *aconnector; + struct drm_connector *wb_connector; + struct drm_audio_component *acomp; + struct device *kdev; + unsigned char buf[DRM_ELD_HEADER_BLOCK_SIZE + 8] = {0}; + bool enabled = false; + int ret; + + kdev = adev->ddev.dev; + KUNIT_ASSERT_NOT_NULL(test, kdev); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acomp); + wb_connector = kunit_kzalloc(test, sizeof(*wb_connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, wb_connector); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, wb_connector, + &dm_test_audio_connector_funcs, + DRM_MODE_CONNECTOR_WRITEBACK), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_audio_connector_cleanup, wb_connector), 0); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconnector->base, + &dm_test_audio_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_audio_connector_cleanup, &aconnector->base), 0); + + mutex_init(&adev->dm.audio_lock); + aconnector->audio_inst = 3; + aconnector->base.eld[DRM_ELD_BASELINE_ELD_LEN] = 2; + aconnector->base.eld[DRM_ELD_VER] = DRM_ELD_VER_CEA861D; + aconnector->base.eld[DRM_ELD_SPEAKER] = DRM_ELD_SPEAKER_FLR; + dev_set_drvdata(kdev, &adev->ddev); + + KUNIT_ASSERT_EQ(test, amdgpu_dm_audio_component_bind(kdev, NULL, acomp), 0); + ret = acomp->ops->get_eld(kdev, 3, 0, &enabled, buf, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, DRM_ELD_HEADER_BLOCK_SIZE + 8); + KUNIT_EXPECT_TRUE(test, enabled); + KUNIT_EXPECT_EQ(test, memcmp(buf, aconnector->base.eld, ret), 0); +} + +/** + * dm_test_audio_component_get_eld_no_match - Test ELD lookup miss + * @test: The KUnit test context + * + * A non-matching connector should leave the audio port disabled and return an + * empty ELD size without copying data. + */ +static void dm_test_audio_component_get_eld_no_match(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_dm_connector *aconnector; + struct drm_audio_component *acomp; + struct device *kdev; + unsigned char buf[DRM_ELD_HEADER_BLOCK_SIZE] = {0x5a}; + bool enabled = true; + int ret; + + kdev = adev->ddev.dev; + KUNIT_ASSERT_NOT_NULL(test, kdev); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acomp); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_EQ(test, drm_connector_init(&adev->ddev, &aconnector->base, + &dm_test_audio_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA), 0); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_audio_connector_cleanup, &aconnector->base), 0); + + mutex_init(&adev->dm.audio_lock); + aconnector->audio_inst = 4; + dev_set_drvdata(kdev, &adev->ddev); + + KUNIT_ASSERT_EQ(test, amdgpu_dm_audio_component_bind(kdev, NULL, acomp), 0); + ret = acomp->ops->get_eld(kdev, 3, 0, &enabled, buf, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_FALSE(test, enabled); + KUNIT_EXPECT_EQ(test, buf[0], 0x5a); +} + static struct kunit_case dm_audio_test_cases[] = { /* amdgpu_dm_audio_init */ KUNIT_CASE(dm_test_audio_init_disabled), @@ -593,6 +709,9 @@ static struct kunit_case dm_audio_test_cases[] = { KUNIT_CASE(dm_test_eld_notify_no_component), KUNIT_CASE(dm_test_eld_notify_null_audio_ops), KUNIT_CASE(dm_test_eld_notify_null_callback), + /* amdgpu_dm_audio_component_get_eld */ + KUNIT_CASE(dm_test_audio_component_get_eld_copies_matching_connector), + KUNIT_CASE(dm_test_audio_component_get_eld_no_match), {} }; -- cgit v1.2.3 From 563c7e12b962af5d71df6d761fd9e1bec8760717 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 13 Jul 2026 14:34:09 -0600 Subject: drm/amd/display: add KUnit tests for audio commit path [WHAT] Add KUnit coverage for amdgpu_dm_commit_audio(), the remaining function reported as untested by gcov. The tests build hand-rolled drm_atomic_state arrays and back dc_stream_get_status() with a fake dc->current_state to cover the removal and addition ELD notifications along with their guard/skip paths: writeback connector, detached connector, missing new CRTC state, no modeset, and absent stream status. Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_audio_test.c | 532 +++++++++++++++++++++ 2 files changed, 533 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c index 4b10c683df8e..199f339839b2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c @@ -321,6 +321,7 @@ notify: amdgpu_dm_audio_eld_notify(adev, inst); } } +EXPORT_IF_KUNIT(amdgpu_dm_commit_audio); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) int amdgpu_dm_audio_get_param(void) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c index d30b7f644f97..040a7a9c0335 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c @@ -8,9 +8,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -37,6 +39,32 @@ static void dm_test_audio_connector_cleanup(void *data) drm_connector_cleanup(data); } +static struct drm_atomic_commit *dm_test_audio_alloc_atomic_state(struct kunit *test, + unsigned int num_connector, + unsigned int num_crtc) +{ + struct drm_atomic_commit *state; + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + state->num_connector = num_connector; + if (num_connector) { + state->connectors = kunit_kcalloc(test, num_connector, + sizeof(*state->connectors), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->connectors); + } + + if (num_crtc) { + state->crtcs = kunit_kcalloc(test, num_crtc, sizeof(*state->crtcs), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state->crtcs); + } + + return state; +} + /* Tests for amdgpu_dm_audio_init() */ /** @@ -411,6 +439,22 @@ static void dm_test_pin_eld_notify(void *audio_ptr, int port, int pipe) dm_test_eld_notify_ptr = audio_ptr; } +static void dm_test_audio_setup_notify_component(struct kunit *test, + struct amdgpu_device *adev) +{ + struct drm_audio_component *acomp; + struct drm_audio_component_audio_ops *audio_ops; + + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acomp); + KUNIT_ASSERT_NOT_NULL(test, audio_ops); + + audio_ops->pin_eld_notify = dm_test_pin_eld_notify; + acomp->audio_ops = audio_ops; + adev->dm.audio_component = acomp; +} + /** * dm_test_eld_notify_invokes_callback - Test ELD notify forwards to hda driver * @test: The KUnit test context @@ -685,6 +729,484 @@ static void dm_test_audio_component_get_eld_no_match(struct kunit *test) KUNIT_EXPECT_EQ(test, buf[0], 0x5a); } +/* Tests for amdgpu_dm_commit_audio() */ + +/** + * dm_test_commit_audio_notifies_removed_connector - Test removal notification + * @test: The KUnit test context + * + * When a connector loses its CRTC, commit_audio should clear its audio + * instance and notify the audio component for the old pin. + */ +static void dm_test_commit_audio_notifies_removed_connector(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + + state = dm_test_audio_alloc_atomic_state(test, 1, 0); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = 5; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + old_conn_state->crtc = crtc; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + + dm_test_eld_notify_count = 0; + dm_test_eld_notify_port = -1; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, -1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 5); +} + +/** + * dm_test_commit_audio_notifies_added_connector - Test addition notification + * @test: The KUnit test context + * + * When a modeset enables a connector with a stream status, commit_audio should + * store the stream audio instance and notify the audio component for that pin. + */ +static void dm_test_commit_audio_notifies_added_connector(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + struct dc_stream_state *stream; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, stream); + + adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc); + adev->dm.dc->current_state = kunit_kzalloc(test, + sizeof(*adev->dm.dc->current_state), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc->current_state); + stream->ctx = kunit_kzalloc(test, sizeof(*stream->ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream->ctx); + stream->ctx->dc = adev->dm.dc; + adev->dm.dc->current_state->stream_count = 1; + adev->dm.dc->current_state->streams[0] = stream; + adev->dm.dc->current_state->stream_status[0].audio_inst = 8; + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = -1; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + crtc->index = 0; + new_conn_state->crtc = crtc; + dm_crtc_state->base.crtc = crtc; + dm_crtc_state->base.mode_changed = true; + dm_crtc_state->stream = stream; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + state->crtcs[0].ptr = crtc; + state->crtcs[0].new_state = &dm_crtc_state->base; + + dm_test_eld_notify_count = 0; + dm_test_eld_notify_port = -1; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 8); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 2); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 8); +} + +/** + * dm_test_commit_audio_skips_writeback_removal - Test writeback removal skip + * @test: The KUnit test context + * + * Writeback connectors do not represent an HDA audio pin, so removal-style + * notifications should leave their audio instance untouched. + */ +static void dm_test_commit_audio_skips_writeback_removal(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + + state = dm_test_audio_alloc_atomic_state(test, 1, 0); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = 9; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; + old_conn_state->crtc = crtc; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 9); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_commit_audio_skips_without_new_crtc_state - Test missing CRTC state + * @test: The KUnit test context + * + * A connector still attached to the same CRTC should not notify if the atomic + * state does not contain a new CRTC state for that CRTC. + */ +static void dm_test_commit_audio_skips_without_new_crtc_state(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = 4; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + crtc->index = 0; + old_conn_state->crtc = crtc; + new_conn_state->crtc = crtc; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 4); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_commit_audio_skips_without_stream_status - Test missing DC status + * @test: The KUnit test context + * + * If the new CRTC stream is absent from the current DC state, additions should + * be skipped because there is no audio instance to publish. + */ +static void dm_test_commit_audio_skips_without_stream_status(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + struct dc_stream_state *stream; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, stream); + + adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc); + adev->dm.dc->current_state = kunit_kzalloc(test, + sizeof(*adev->dm.dc->current_state), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc->current_state); + stream->ctx = kunit_kzalloc(test, sizeof(*stream->ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream->ctx); + stream->ctx->dc = adev->dm.dc; + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = -1; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + crtc->index = 0; + new_conn_state->crtc = crtc; + dm_crtc_state->base.crtc = crtc; + dm_crtc_state->base.mode_changed = true; + dm_crtc_state->stream = stream; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + state->crtcs[0].ptr = crtc; + state->crtcs[0].new_state = &dm_crtc_state->base; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, -1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1); +} + +/** + * dm_test_commit_audio_skips_detached_connector - Test detached connector skip + * @test: The KUnit test context + * + * A connector that remains detached across the commit should not notify or + * alter the stored audio instance. + */ +static void dm_test_commit_audio_skips_detached_connector(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + + state = dm_test_audio_alloc_atomic_state(test, 1, 0); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + + aconnector->audio_inst = 11; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 11); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_commit_audio_skips_without_modeset - Test no-modeset skip + * @test: The KUnit test context + * + * A connector that stays on the same CRTC should not notify if that CRTC does + * not need a modeset. + */ +static void dm_test_commit_audio_skips_without_modeset(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + aconnector->audio_inst = 12; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + crtc->index = 0; + old_conn_state->crtc = crtc; + new_conn_state->crtc = crtc; + dm_crtc_state->base.crtc = crtc; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + state->crtcs[0].ptr = crtc; + state->crtcs[0].new_state = &dm_crtc_state->base; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 12); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_commit_audio_skips_addition_without_stream - Test NULL stream skip + * @test: The KUnit test context + * + * A modeset still sends the removal-side ELD notification, but the addition + * side must skip if the new DM CRTC state has no stream. + */ +static void dm_test_commit_audio_skips_addition_without_stream(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = 13; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + crtc->index = 0; + old_conn_state->crtc = crtc; + new_conn_state->crtc = crtc; + dm_crtc_state->base.crtc = crtc; + dm_crtc_state->base.mode_changed = true; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + state->crtcs[0].ptr = crtc; + state->crtcs[0].new_state = &dm_crtc_state->base; + + dm_test_eld_notify_count = 0; + dm_test_eld_notify_port = -1; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, -1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 13); +} + +/** + * dm_test_commit_audio_skips_writeback_addition - Test writeback addition skip + * @test: The KUnit test context + * + * Even with a valid modeset and stream status, writeback connectors must not + * publish an HDA audio instance. + */ +static void dm_test_commit_audio_skips_writeback_addition(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_atomic_commit *state; + struct amdgpu_dm_connector *aconnector; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_crtc *crtc; + struct dm_crtc_state *dm_crtc_state; + struct dc_stream_state *stream; + + state = dm_test_audio_alloc_atomic_state(test, 1, 1); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + dm_crtc_state = kunit_kzalloc(test, sizeof(*dm_crtc_state), GFP_KERNEL); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, dm_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, stream); + + adev->dm.dc = kunit_kzalloc(test, sizeof(*adev->dm.dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc); + adev->dm.dc->current_state = kunit_kzalloc(test, + sizeof(*adev->dm.dc->current_state), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev->dm.dc->current_state); + stream->ctx = kunit_kzalloc(test, sizeof(*stream->ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream->ctx); + stream->ctx->dc = adev->dm.dc; + adev->dm.dc->current_state->stream_count = 1; + adev->dm.dc->current_state->streams[0] = stream; + adev->dm.dc->current_state->stream_status[0].audio_inst = 14; + + mutex_init(&adev->dm.audio_lock); + dm_test_audio_setup_notify_component(test, adev); + aconnector->audio_inst = 15; + aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; + crtc->index = 0; + new_conn_state->crtc = crtc; + dm_crtc_state->base.crtc = crtc; + dm_crtc_state->base.mode_changed = true; + dm_crtc_state->stream = stream; + state->connectors[0].ptr = &aconnector->base; + state->connectors[0].old_state = old_conn_state; + state->connectors[0].new_state = new_conn_state; + state->crtcs[0].ptr = crtc; + state->crtcs[0].new_state = &dm_crtc_state->base; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_commit_audio(&adev->ddev, state); + + KUNIT_EXPECT_EQ(test, aconnector->audio_inst, 15); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + static struct kunit_case dm_audio_test_cases[] = { /* amdgpu_dm_audio_init */ KUNIT_CASE(dm_test_audio_init_disabled), @@ -712,6 +1234,16 @@ static struct kunit_case dm_audio_test_cases[] = { /* amdgpu_dm_audio_component_get_eld */ KUNIT_CASE(dm_test_audio_component_get_eld_copies_matching_connector), KUNIT_CASE(dm_test_audio_component_get_eld_no_match), + /* amdgpu_dm_commit_audio */ + KUNIT_CASE(dm_test_commit_audio_notifies_removed_connector), + KUNIT_CASE(dm_test_commit_audio_notifies_added_connector), + KUNIT_CASE(dm_test_commit_audio_skips_writeback_removal), + KUNIT_CASE(dm_test_commit_audio_skips_without_new_crtc_state), + KUNIT_CASE(dm_test_commit_audio_skips_without_stream_status), + KUNIT_CASE(dm_test_commit_audio_skips_detached_connector), + KUNIT_CASE(dm_test_commit_audio_skips_without_modeset), + KUNIT_CASE(dm_test_commit_audio_skips_addition_without_stream), + KUNIT_CASE(dm_test_commit_audio_skips_writeback_addition), {} }; -- cgit v1.2.3 From 86507b581fb74b7155954b00d79e0a7b4db17594 Mon Sep 17 00:00:00 2001 From: Tomasz Siemek Date: Wed, 15 Jul 2026 13:37:52 +0200 Subject: drm/amd/display: Use current mpc pipe in set output transfer func [why] Only the top pipe had the output transfer function programmed. [how] Program the current pipe from the MPC chain during fast sequence build. Reviewed-by: Martin Leung Reviewed-by: Alvin Lee Signed-off-by: Tomasz Siemek Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c index 8b54bab98283..11411fa94665 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c @@ -1111,7 +1111,7 @@ void hwss_build_fast_sequence(struct dc *dc, (*num_steps)++; } } - if (current_mpc_pipe->stream->update_flags.bits.out_tf) { + if (hws->funcs.set_output_transfer_func && current_mpc_pipe->stream->update_flags.bits.out_tf) { struct set_output_transfer_func_params *otf_params = &block_sequence[*num_steps].params.set_output_transfer_func_params; @@ -1119,7 +1119,7 @@ void hwss_build_fast_sequence(struct dc *dc, otf_params->xfm = current_mpc_pipe->plane_res.xfm; otf_params->mpc = dc->res_pool->mpc; otf_params->mpcc_id = current_mpc_pipe->plane_res.hubp->inst; - otf_params->is_top_pipe = resource_is_pipe_type(pipe_ctx, OPP_HEAD); + otf_params->is_top_pipe = resource_is_pipe_type(current_mpc_pipe, OPP_HEAD); otf_params->stream = current_mpc_pipe->stream; block_sequence[*num_steps].func = DPP_SET_OUTPUT_TRANSFER_FUNC; (*num_steps)++; -- cgit v1.2.3 From b208e33f6017b5362f7c8192a7e08a8e5c901828 Mon Sep 17 00:00:00 2001 From: Nicholas Carbones Date: Tue, 14 Jul 2026 14:06:36 -0400 Subject: drm/amd/display: Correct vblank_end calc for fams cmd packet [Why] By changing how we populate vblank_nom, constraining vblank size, the vblank_end calculation for the fams command packet gets affected. We add up v_active with a new vblank_nom value for stream pstate's nom_vtotal, which describes the vtotal and nominal refresh rate, and that causes underflow. [How] Set nom_vtotal equal to the timing's v_total. Reviewed-by: Dillon Varone Signed-off-by: Nicholas Carbones Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c index d5f20c85006e..8f481ef9316f 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn4_fams2.c @@ -1710,7 +1710,7 @@ static void build_pstate_meta_per_stream(struct dml2_pmo_instance *pmo, /* common */ stream_pstate_meta->valid = true; stream_pstate_meta->otg_vline_time_us = (double)timing->h_total / timing->pixel_clock_khz * 1000.0; - stream_pstate_meta->nom_vtotal = stream_descriptor->timing.vblank_nom + stream_descriptor->timing.v_active; + stream_pstate_meta->nom_vtotal = stream_descriptor->timing.v_total; stream_pstate_meta->nom_refresh_rate_hz = timing->pixel_clock_khz * 1000.0 / (stream_pstate_meta->nom_vtotal * timing->h_total); stream_pstate_meta->nom_frame_time_us = -- cgit v1.2.3 From 36e7f55ec8a9c3b10fd5d19f9dac1f33dc864d61 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 13 Jul 2026 17:09:17 -0600 Subject: drm/amd/display: Add KUnit test for native backlight registration [WHAT] Add a KUnit case covering the successful path of amdgpu_dm_register_backlight_device(). The test uses the standard DRM KUnit device helpers to register a native backlight device, verifies the calculated backlight properties and cached brightness, and unregisters the device through KUnit cleanup to avoid leaking a class device across cases. Assisted-by: Copilot:GPT-5.6-Terra Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index 618fd5142da0..7ca17f803f9d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -326,6 +326,11 @@ static int dm_test_get_backlight_level_error(const struct dc_link *link) return DC_ERROR_UNEXPECTED; } +static void dm_test_unregister_backlight_device(void *data) +{ + backlight_device_unregister(data); +} + static bool dm_test_get_backlight_level_nits(struct dc_link *link, uint32_t *avg, uint32_t *peak) @@ -463,6 +468,52 @@ static void dm_test_register_backlight_device_negative_index(struct kunit *test) KUNIT_EXPECT_NULL(test, adev->dm.backlight_dev[0]); } +/** + * dm_test_register_backlight_device_success - Test native backlight registration + * @test: The KUnit test context + * + * A native backlight device must be registered with the calculated + * properties, and its initial readback must retain the initial brightness. + */ +static void dm_test_register_backlight_device_success(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct amdgpu_dm_connector *aconnector; + struct amdgpu_dm_backlight_caps *caps; + struct dc_link *link = dm_kunit_alloc_link(test); + struct drm_minor *primary; + unsigned int max; + + setup_test_link_service(test, link); + link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_error; + primary = kunit_kzalloc(test, sizeof(*primary), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary); + adev->ddev.primary = primary; + adev->dm.backlight_link[0] = link; + + aconnector = dm_kunit_alloc_connector(test, adev, link); + aconnector->bl_idx = 0; + aconnector->base.kdev = adev->ddev.dev; + caps = &adev->dm.backlight_caps[0]; + caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps->ac_level = 50; + caps->dc_level = 25; + max = 0x101 * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + + amdgpu_dm_register_backlight_device(aconnector); + + KUNIT_ASSERT_NOT_NULL(test, adev->dm.backlight_dev[0]); + KUNIT_EXPECT_EQ(test, adev->dm.backlight_dev[0]->props.max_brightness, max); + KUNIT_EXPECT_EQ(test, adev->dm.backlight_dev[0]->props.brightness, + DIV_ROUND_CLOSEST(max * caps->ac_level, 100)); + KUNIT_EXPECT_EQ(test, adev->dm.brightness[0], + adev->dm.backlight_dev[0]->props.brightness); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, + dm_test_unregister_backlight_device, + adev->dm.backlight_dev[0]), 0); +} + static struct drm_connector *setup_panel_power_savings_connector(struct kunit *test, struct device **device_out, struct dm_connector_state **state_out) @@ -1861,6 +1912,7 @@ static struct kunit_case dm_backlight_test_cases[] = { KUNIT_CASE(dm_test_backlight_get_brightness_uses_device_index), /* amdgpu_dm_register_backlight_device */ KUNIT_CASE(dm_test_register_backlight_device_negative_index), + KUNIT_CASE(dm_test_register_backlight_device_success), /* panel_power_savings_show / store */ KUNIT_CASE(dm_test_panel_power_savings_show_maps_disable_to_zero), KUNIT_CASE(dm_test_panel_power_savings_show_reports_level), -- cgit v1.2.3 From bfab0b0efd809de5d9cc81f0d6e678148c5eb43f Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 14 Jul 2026 15:03:05 -0600 Subject: drm/amd/display: Add color transfer-function tests [WHY] The color transfer-function calculation helpers were not covered by the amdgpu_dm_color KUnit suite. They rely on DAL fixed-point math and run under the KUnit UML build without native floating point. [HOW] Expose the five static helpers to KUnit and add direct coverage for the legacy, 16-bit, and 32-bit input and output transfer-function paths, including sRGB, linear, and ROM-backed variants. Assisted-by: Copilot:GPT-5.6-Sol Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 36 +++-- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.h | 14 ++ .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 147 +++++++++++++++++++++ 3 files changed, 184 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 658dfc37d4d4..7b68c6846039 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -618,9 +618,10 @@ EXPORT_IF_KUNIT(__drm_ctm_3x4_to_dc_matrix); * Returns: * 0 in case of success, -ENOMEM if fails */ -static int __set_legacy_tf(struct dc_transfer_func *func, - const struct drm_color_lut *lut, uint32_t lut_size, - bool has_rom) +STATIC_IFN_KUNIT int +__set_legacy_tf(struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size, + bool has_rom) { struct dc_gamma *gamma = NULL; struct calculate_buffer cal_buffer = {0}; @@ -645,6 +646,7 @@ static int __set_legacy_tf(struct dc_transfer_func *func, return res ? 0 : -ENOMEM; } +EXPORT_IF_KUNIT(__set_legacy_tf); /** * __set_output_tf - calculates the output transfer function based on expected input space. @@ -656,9 +658,10 @@ static int __set_legacy_tf(struct dc_transfer_func *func, * Returns: * 0 in case of success. -ENOMEM if fails. */ -static int __set_output_tf(struct dc_transfer_func *func, - const struct drm_color_lut *lut, uint32_t lut_size, - bool has_rom) +STATIC_IFN_KUNIT int +__set_output_tf(struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size, + bool has_rom) { struct dc_gamma *gamma = NULL; struct calculate_buffer cal_buffer = {0}; @@ -703,6 +706,7 @@ static int __set_output_tf(struct dc_transfer_func *func, return res ? 0 : -ENOMEM; } +EXPORT_IF_KUNIT(__set_output_tf); /** * __set_output_tf_32 - calculates the output transfer function based on expected input space. @@ -714,9 +718,10 @@ static int __set_output_tf(struct dc_transfer_func *func, * Returns: * 0 in case of success. -ENOMEM if fails. */ -static int __set_output_tf_32(struct dc_transfer_func *func, - const struct drm_color_lut32 *lut, uint32_t lut_size, - bool has_rom) +STATIC_IFN_KUNIT int +__set_output_tf_32(struct dc_transfer_func *func, + const struct drm_color_lut32 *lut, uint32_t lut_size, + bool has_rom) { struct dc_gamma *gamma = NULL; struct calculate_buffer cal_buffer = {0}; @@ -759,6 +764,7 @@ static int __set_output_tf_32(struct dc_transfer_func *func, return res ? 0 : -ENOMEM; } +EXPORT_IF_KUNIT(__set_output_tf_32); STATIC_IFN_KUNIT void __set_tf_bypass(struct dc_transfer_func *tf) { @@ -820,8 +826,9 @@ EXPORT_IF_KUNIT(amdgpu_dm_set_atomic_regamma); * Returns: * 0 in case of success. -ENOMEM if fails. */ -static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *func, - const struct drm_color_lut *lut, uint32_t lut_size) +STATIC_IFN_KUNIT int __set_input_tf(struct dc_color_caps *caps, + struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size) { struct dc_gamma *gamma = NULL; bool res; @@ -844,6 +851,7 @@ static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *f return res ? 0 : -ENOMEM; } +EXPORT_IF_KUNIT(__set_input_tf); /** * __set_input_tf_32 - calculates the input transfer function based on expected @@ -856,8 +864,9 @@ static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *f * Returns: * 0 in case of success. -ENOMEM if fails. */ -static int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func *func, - const struct drm_color_lut32 *lut, uint32_t lut_size) +STATIC_IFN_KUNIT int __set_input_tf_32(struct dc_color_caps *caps, + struct dc_transfer_func *func, + const struct drm_color_lut32 *lut, uint32_t lut_size) { struct dc_gamma *gamma = NULL; bool res; @@ -880,6 +889,7 @@ static int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func return res ? 0 : -ENOMEM; } +EXPORT_IF_KUNIT(__set_input_tf_32); STATIC_IFN_KUNIT enum dc_transfer_func_predefined diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h index cec23a020c3d..3e8a3d3fc855 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h @@ -64,6 +64,20 @@ void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, struct fixed31_32 *matrix); void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm, struct fixed31_32 *matrix); +int __set_legacy_tf(struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size, + bool has_rom); +int __set_output_tf(struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size, + bool has_rom); +int __set_output_tf_32(struct dc_transfer_func *func, + const struct drm_color_lut32 *lut, uint32_t lut_size, + bool has_rom); +struct dc_color_caps; +int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size); +int __set_input_tf_32(struct dc_color_caps *caps, struct dc_transfer_func *func, + const struct drm_color_lut32 *lut, uint32_t lut_size); enum dc_transfer_func_predefined amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf); enum dc_transfer_func_predefined diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index e76e533a8323..de11a606a88c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -1375,6 +1375,146 @@ static void dm_test_set_tf_distributed_points_pq(struct kunit *test) KUNIT_EXPECT_EQ(test, tf->sdr_ref_white_level, 80U); } +/** + * dm_test_set_legacy_tf_identity - Legacy identity LUT uses the sRGB ROM path + * @test: KUnit test context + */ +static void dm_test_set_legacy_tf_identity(struct kunit *test) +{ + struct drm_color_lut *lut; + struct dc_transfer_func *tf; + int i; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + lut = kunit_kcalloc(test, MAX_COLOR_LEGACY_LUT_ENTRIES, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + + for (i = 0; i < MAX_COLOR_LEGACY_LUT_ENTRIES; i++) { + u16 value = i * MAX_DRM_LUT_VALUE / (MAX_COLOR_LEGACY_LUT_ENTRIES - 1); + + lut[i].red = value; + lut[i].green = value; + lut[i].blue = value; + } + + tf->type = TF_TYPE_PREDEFINED; + tf->tf = TRANSFER_FUNCTION_SRGB; + + KUNIT_EXPECT_EQ(test, + __set_legacy_tf(tf, lut, MAX_COLOR_LEGACY_LUT_ENTRIES, true), + 0); +} + +/** + * dm_test_set_output_tf_linear - Linear output without a LUT calculates degamma + * @test: KUnit test context + */ +static void dm_test_set_output_tf_linear(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + tf->type = TF_TYPE_PREDEFINED; + tf->tf = TRANSFER_FUNCTION_LINEAR; + + KUNIT_EXPECT_EQ(test, __set_output_tf(tf, NULL, 0, false), 0); +} + +/** + * dm_test_set_output_tf_32_srgb_rom - sRGB output uses the no-LUT ROM path + * @test: KUnit test context + */ +static void dm_test_set_output_tf_32_srgb_rom(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + tf->type = TF_TYPE_PREDEFINED; + tf->tf = TRANSFER_FUNCTION_SRGB; + + KUNIT_EXPECT_EQ(test, __set_output_tf_32(tf, NULL, 0, true), 0); +} + +/** + * dm_test_set_input_tf_srgb - Predefined sRGB input needs no generated curve + * @test: KUnit test context + */ +static void dm_test_set_input_tf_srgb(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + tf->type = TF_TYPE_PREDEFINED; + tf->tf = TRANSFER_FUNCTION_SRGB; + + KUNIT_EXPECT_EQ(test, __set_input_tf(NULL, tf, NULL, 0), 0); +} + +/** + * dm_test_set_input_tf_32_srgb - 32-bit input wrapper accepts predefined sRGB + * @test: KUnit test context + */ +static void dm_test_set_input_tf_32_srgb(struct kunit *test) +{ + struct dc_transfer_func *tf; + + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + tf->type = TF_TYPE_PREDEFINED; + tf->tf = TRANSFER_FUNCTION_SRGB; + + KUNIT_EXPECT_EQ(test, __set_input_tf_32(NULL, tf, NULL, 0), 0); +} + +/** + * dm_test_set_transfer_funcs_with_luts - LUT-backed transfer functions succeed + * @test: KUnit test context + */ +static void dm_test_set_transfer_funcs_with_luts(struct kunit *test) +{ + struct drm_color_lut32 *lut32; + struct drm_color_lut *lut; + struct dc_transfer_func *tf; + + lut = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, sizeof(*lut), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut); + lut32 = kunit_kcalloc(test, MAX_COLOR_LUT_ENTRIES, sizeof(*lut32), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, lut32); + tf = kunit_kzalloc(test, sizeof(*tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tf); + + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = TRANSFER_FUNCTION_LINEAR; + KUNIT_EXPECT_EQ(test, + __set_output_tf(tf, lut, MAX_COLOR_LUT_ENTRIES, false), + 0); + + memset(tf, 0, sizeof(*tf)); + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = TRANSFER_FUNCTION_LINEAR; + KUNIT_EXPECT_EQ(test, + __set_output_tf_32(tf, lut32, MAX_COLOR_LUT_ENTRIES, false), + 0); + + memset(tf, 0, sizeof(*tf)); + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = TRANSFER_FUNCTION_SRGB; + KUNIT_EXPECT_EQ(test, + __set_input_tf(NULL, tf, lut, MAX_COLOR_LUT_ENTRIES), + 0); + + memset(tf, 0, sizeof(*tf)); + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = TRANSFER_FUNCTION_SRGB; + KUNIT_EXPECT_EQ(test, + __set_input_tf_32(NULL, tf, lut32, MAX_COLOR_LUT_ENTRIES), + 0); +} + /** * dm_test_set_atomic_regamma_bypass - No LUT and linear TF: must take bypass path * @test: KUnit test context @@ -2384,6 +2524,13 @@ static struct kunit_case dm_color_test_cases[] = { /* __set_tf_distributed_points */ KUNIT_CASE(dm_test_set_tf_distributed_points_srgb), KUNIT_CASE(dm_test_set_tf_distributed_points_pq), + /* Transfer-function calculation helpers */ + KUNIT_CASE(dm_test_set_legacy_tf_identity), + KUNIT_CASE(dm_test_set_output_tf_linear), + KUNIT_CASE(dm_test_set_output_tf_32_srgb_rom), + KUNIT_CASE(dm_test_set_input_tf_srgb), + KUNIT_CASE(dm_test_set_input_tf_32_srgb), + KUNIT_CASE(dm_test_set_transfer_funcs_with_luts), /* amdgpu_dm_set_atomic_regamma */ KUNIT_CASE(dm_test_set_atomic_regamma_bypass), /* amdgpu_dm_atomic_shaper_lut */ -- cgit v1.2.3 From 52d0db4dfc29bd4f42ff7617bd8de386af9b3afc Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 14 Jul 2026 15:09:29 -0600 Subject: drm/amd/display: Add atomic transfer-function tests [WHY] The atomic regamma, shaper, and blend helpers were only exercised on their linear bypass paths, leaving the generated transfer-function cases untested. [HOW] Add sRGB success-path tests that verify the generated transfer functions and the corresponding plane color-management enable flags. Assisted-by: Copilot:GPT-5.6-Sol Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index de11a606a88c..4945f5429bc1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -1535,6 +1535,23 @@ static void dm_test_set_atomic_regamma_bypass(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)out_tf->tf, (int)TRANSFER_FUNCTION_LINEAR); } +/** + * dm_test_set_atomic_regamma_srgb - Non-linear TF enables generated regamma + * @test: KUnit test context + */ +static void dm_test_set_atomic_regamma_srgb(struct kunit *test) +{ + struct dc_transfer_func *out_tf; + + out_tf = kunit_kzalloc(test, sizeof(*out_tf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, out_tf); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_set_atomic_regamma(out_tf, NULL, 0, true, TRANSFER_FUNCTION_SRGB), + 0); + KUNIT_EXPECT_EQ(test, (int)out_tf->type, (int)TF_TYPE_DISTRIBUTED_POINTS); +} + /** * dm_test_atomic_shaper_lut_bypass - No LUT and linear TF: must take bypass path * @test: KUnit test context @@ -1577,6 +1594,28 @@ static void dm_test_atomic_blend_lut_bypass(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)cm->blend_func.tf, (int)TRANSFER_FUNCTION_LINEAR); } +/** + * dm_test_atomic_shaper_blend_srgb - Non-linear TFs enable shaper and blend + * @test: KUnit test context + */ +static void dm_test_atomic_shaper_blend_srgb(struct kunit *test) +{ + struct dc_plane_cm *cm; + + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_atomic_shaper_lut(NULL, true, TRANSFER_FUNCTION_SRGB, 0, cm), + 0); + KUNIT_EXPECT_TRUE(test, cm->flags.bits.shaper_enable); + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_atomic_blend_lut(NULL, false, TRANSFER_FUNCTION_SRGB, 0, cm), + 0); + KUNIT_EXPECT_TRUE(test, cm->flags.bits.blend_enable); +} + /* ---- Tests for __set_colorop_in_tf_1d_curve ---- */ /** @@ -2533,10 +2572,12 @@ static struct kunit_case dm_color_test_cases[] = { KUNIT_CASE(dm_test_set_transfer_funcs_with_luts), /* amdgpu_dm_set_atomic_regamma */ KUNIT_CASE(dm_test_set_atomic_regamma_bypass), + KUNIT_CASE(dm_test_set_atomic_regamma_srgb), /* amdgpu_dm_atomic_shaper_lut */ KUNIT_CASE(dm_test_atomic_shaper_lut_bypass), /* amdgpu_dm_atomic_blend_lut */ KUNIT_CASE(dm_test_atomic_blend_lut_bypass), + KUNIT_CASE(dm_test_atomic_shaper_blend_srgb), /* __set_colorop_in_tf_1d_curve */ KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_invalid_type), KUNIT_CASE(dm_test_set_colorop_in_tf_1d_curve_unsupported_curve), -- cgit v1.2.3 From 298eb60f14d4be776c9e3c6759c834f5e327dca5 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 14 Jul 2026 15:16:32 -0600 Subject: drm/amd/display: Add CRTC and plane degamma tests [WHY] CRTC degamma classification and its mapping onto the plane input transfer function lacked both success and conflict coverage. [HOW] Exercise legacy regamma detection, atomic and plane degamma LUTs, BT.709 video mapping, and rejection of simultaneous degamma stages. Assisted-by: Copilot:GPT-5.6-Sol Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 128 +++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index 4945f5429bc1..0835177c98d2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -2121,6 +2121,36 @@ static void dm_test_check_crtc_color_mgmt_no_luts(struct kunit *test) KUNIT_EXPECT_FALSE(test, f.crtc_state->cm_is_degamma_srgb); } +/** + * dm_test_check_crtc_color_mgmt_legacy_regamma - Legacy LUT uses sRGB regamma + * @test: KUnit test context + */ +static void dm_test_check_crtc_color_mgmt_legacy_regamma(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.crtc_state->base.gamma_lut = dm_test_make_lut_blob(test, MAX_COLOR_LEGACY_LUT_ENTRIES); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_check_crtc_color_mgmt(f.crtc_state, true), 0); + KUNIT_EXPECT_TRUE(test, f.crtc_state->cm_is_degamma_srgb); +} + +/** + * dm_test_check_crtc_color_mgmt_degamma - Atomic degamma is mapped to the plane + * @test: KUnit test context + */ +static void dm_test_check_crtc_color_mgmt_degamma(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + + f.crtc_state->base.degamma_lut = + dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_check_crtc_color_mgmt(f.crtc_state, true), 0); + KUNIT_EXPECT_TRUE(test, f.crtc_state->cm_has_degamma); + KUNIT_EXPECT_FALSE(test, f.crtc_state->cm_is_degamma_srgb); +} + /** * dm_test_update_crtc_color_mgmt_no_ctm - No CRTC CTM leaves remap bypassed * @test: KUnit test context @@ -2214,6 +2244,98 @@ static void dm_test_update_plane_color_mgmt_maps_crtc_degamma(struct kunit *test (int)TRANSFER_FUNCTION_SRGB); } +/** + * dm_test_update_plane_color_mgmt_maps_crtc_degamma_lut - CRTC LUT maps to plane degamma + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_maps_crtc_degamma_lut(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + int ret; + + f.crtc_state->cm_has_degamma = true; + f.crtc_state->cm_is_degamma_srgb = true; + f.crtc_state->base.degamma_lut = + dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.type, + (int)TF_TYPE_DISTRIBUTED_POINTS); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.tf, + (int)TRANSFER_FUNCTION_SRGB); +} + +/** + * dm_test_update_plane_color_mgmt_maps_bt709_degamma - Video uses BT.709 degamma + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_maps_bt709_degamma(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + int ret; + + f.crtc_state->cm_is_degamma_srgb = true; + f.dc_plane_state->format = SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.tf, + (int)TRANSFER_FUNCTION_BT709); +} + +/** + * dm_test_update_plane_color_mgmt_plane_degamma_lut - Non-linear plane LUT succeeds + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_plane_degamma_lut(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct drm_color_lut *lut; + int ret; + + f.dm_plane_state->degamma_lut = + dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + lut = f.dm_plane_state->degamma_lut->data; + lut[0].red = MAX_DRM_LUT_VALUE; + lut[0].green = MAX_DRM_LUT_VALUE; + lut[0].blue = MAX_DRM_LUT_VALUE; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->in_transfer_func.type, + (int)TF_TYPE_DISTRIBUTED_POINTS); +} + +/** + * dm_test_update_plane_color_mgmt_rejects_dual_degamma - Reject two degamma stages + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_rejects_dual_degamma(struct kunit *test) +{ + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct drm_crtc *crtc; + int ret; + + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc); + crtc->dev = &f.adev->ddev; + f.crtc_state->base.crtc = crtc; + f.crtc_state->cm_has_degamma = true; + f.dm_plane_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + /** * dm_test_update_plane_color_mgmt_uses_color_caps - DC context provides color caps * @test: KUnit test context @@ -2600,11 +2722,17 @@ static struct kunit_case dm_color_test_cases[] = { KUNIT_CASE(dm_test_colorop_degamma_no_match), /* CRTC and plane color management update paths */ KUNIT_CASE(dm_test_check_crtc_color_mgmt_no_luts), + KUNIT_CASE(dm_test_check_crtc_color_mgmt_legacy_regamma), + KUNIT_CASE(dm_test_check_crtc_color_mgmt_degamma), KUNIT_CASE(dm_test_update_crtc_color_mgmt_no_ctm), KUNIT_CASE(dm_test_update_crtc_color_mgmt_ctm), KUNIT_CASE(dm_test_update_plane_color_mgmt_bad_lut3d), KUNIT_CASE(dm_test_update_plane_color_mgmt_fallback_defaults), KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_crtc_degamma), + KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_crtc_degamma_lut), + KUNIT_CASE(dm_test_update_plane_color_mgmt_maps_bt709_degamma), + KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_degamma_lut), + KUNIT_CASE(dm_test_update_plane_color_mgmt_rejects_dual_degamma), KUNIT_CASE(dm_test_update_plane_color_mgmt_uses_color_caps), KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_ctm), KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_bypass_pipeline), -- cgit v1.2.3 From b99214c2d830adeb9720d3f46d781bad0c56c093 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 14 Jul 2026 15:22:36 -0600 Subject: drm/amd/display: Add legacy plane LUT tests [WHY] The legacy plane color path did not exercise successful shaper, 3D LUT, and blend LUT programming as a single transaction. [HOW] Provide valid legacy LUT blobs and verify that all three plane color-management stages are enabled. Assisted-by: Copilot:GPT-5.6-Sol Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index 0835177c98d2..025e408a8ae6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -2355,6 +2355,33 @@ static void dm_test_update_plane_color_mgmt_uses_color_caps(struct kunit *test) 0); } +/** + * dm_test_update_plane_color_mgmt_legacy_luts - Legacy shaper and blend LUTs succeed + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_legacy_luts(struct kunit *test) +{ + const u32 lut3d_entries = + MAX_COLOR_3DLUT_SIZE * MAX_COLOR_3DLUT_SIZE * MAX_COLOR_3DLUT_SIZE; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + int ret; + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->shaper_lut = + dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + f.dm_plane_state->lut3d = dm_test_make_lut_blob(test, lut3d_entries); + f.dm_plane_state->blend_lut = + dm_test_make_lut_blob(test, MAX_COLOR_LUT_ENTRIES); + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.blend_enable); +} + /** * dm_test_update_plane_color_mgmt_plane_ctm - Plane CTM maps to gamut remap * @test: KUnit test context @@ -2734,6 +2761,7 @@ static struct kunit_case dm_color_test_cases[] = { KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_degamma_lut), KUNIT_CASE(dm_test_update_plane_color_mgmt_rejects_dual_degamma), KUNIT_CASE(dm_test_update_plane_color_mgmt_uses_color_caps), + KUNIT_CASE(dm_test_update_plane_color_mgmt_legacy_luts), KUNIT_CASE(dm_test_update_plane_color_mgmt_plane_ctm), KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_bypass_pipeline), KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_multiplier), -- cgit v1.2.3 From 81f1d7bd953e8ac0239423259b9485e72e52ae3a Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 14 Jul 2026 15:28:49 -0600 Subject: drm/amd/display: Add truncated colorop tests [WHY] Truncated colorop pipelines that end after the 3x4 matrix had no coverage for their fallback exits. [HOW] Exercise every remaining pipeline length with bypassed operations and verify that the plane color stages stay disabled. Assisted-by: Copilot:GPT-5.6-Sol Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index 025e408a8ae6..11d42ff59dff 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -2581,6 +2581,57 @@ static void dm_test_update_plane_color_mgmt_colorop_missing_3x4(struct kunit *te KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); } +/** + * dm_test_update_plane_color_mgmt_colorop_truncated - truncated pipelines fall back + * @test: KUnit test context + * + * Covers each pipeline exit after the 3x4 matrix while all present colorops + * remain bypassed, avoiding the floating-point color calculation paths. + */ +static void dm_test_update_plane_color_mgmt_colorop_truncated(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, true, true, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct dc_plane_state *dc_plane_state = f.dc_plane_state; + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct dm_crtc_state *crtc_state = f.crtc_state; + int count, ret; + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + + for (count = 3; count <= ARRAY_SIZE(types); count++) { + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, count); + memset(f.dc_plane_state, 0, sizeof(*f.dc_plane_state)); + + ret = amdgpu_dm_update_plane_color_mgmt(crtc_state, plane_state, dc_plane_state); + KUNIT_EXPECT_EQ_MSG(test, ret, 0, "pipeline length %d should fall back", count); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.blend_enable); + } +} + /** * dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw - no 3D LUT skips 3D ops * @test: KUnit test context @@ -2766,6 +2817,7 @@ static struct kunit_case dm_color_test_cases[] = { KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_bypass_pipeline), KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_multiplier), KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_3x4), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_truncated), KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw), {} }; -- cgit v1.2.3 From 9b03693912868cfc0272ff042416381f29e47b80 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 14 Jul 2026 15:29:08 -0600 Subject: drm/amd/display: Add colorop LUT programming tests [WHY] The enabled colorop shaper, 3D LUT, and blend programming paths were uncovered, including the fallback for missing 3D LUT data. [HOW] Build complete colorop pipelines with valid LUT blobs, verify each enabled stage, and cover the empty 3D LUT fallback. Assisted-by: Copilot:GPT-5.6-Sol Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 206 +++++++++++++++++++++ 1 file changed, 206 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index 11d42ff59dff..8c2f7ab5f8d3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -2676,6 +2676,208 @@ static void dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw(struct kunit *te KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); } +/** + * dm_test_update_plane_color_mgmt_colorop_shaper - enabled shaper TF and LUT succeed + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_shaper(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, false, false, true, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_colorop_state *shaper_lut_state; + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct drm_property_blob *blob; + int ret; + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, + ARRAY_SIZE(types)); + shaper_lut_state = f.state->colorops[4].new_state; + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->length = MAX_COLOR_LUT_ENTRIES * sizeof(struct drm_color_lut32); + blob->data = kunit_kzalloc(test, blob->length, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob->data); + shaper_lut_state->data = blob; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.shaper_enable); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.shaper_func.type, + (int)TF_TYPE_DISTRIBUTED_POINTS); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_3dlut - enabled 3D LUT succeeds + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_3dlut(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, true, true, false, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_colorop_state *lut3d_state; + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct drm_property_blob *blob; + int ret; + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, + ARRAY_SIZE(types)); + lut3d_state = f.state->colorops[5].new_state; + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->length = 5 * sizeof(struct drm_color_lut32); + blob->data = kunit_kzalloc(test, blob->length, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob->data); + lut3d_state->data = blob; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.shaper_func.type, + (int)TF_TYPE_DISTRIBUTED_POINTS); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.shaper_func.tf, + (int)TRANSFER_FUNCTION_LINEAR); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_3dlut_no_data - empty 3D LUT falls back + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_3dlut_no_data(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { + true, true, true, true, true, false, true, true, + }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + int ret; + + f.adev->dm.dc->caps.color.dpp.hw_3d_lut = true; + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, + ARRAY_SIZE(types)); + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_FALSE(test, f.dc_plane_state->cm.flags.bits.lut3d_enable); +} + +/** + * dm_test_update_plane_color_mgmt_colorop_blend - enabled blend TF and LUT succeed + * @test: KUnit test context + */ +static void dm_test_update_plane_color_mgmt_colorop_blend(struct kunit *test) +{ + static const enum drm_colorop_type types[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + static const enum drm_colorop_curve_1d_type curves[] = { + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + DRM_COLOROP_1D_CURVE_SRGB_EOTF, + }; + static const bool bypass[] = { true, true, true, false, false }; + struct dm_test_color_update_fixture f = dm_test_color_update_setup(test); + struct drm_colorop_state *blend_lut_state; + struct drm_plane_state *plane_state = &f.dm_plane_state->base; + struct drm_property_blob *blob; + int ret; + + f.dm_plane_state->base.color_pipeline = + dm_test_colorop_pipeline_setup(test, &f, types, curves, bypass, + ARRAY_SIZE(types)); + blend_lut_state = f.state->colorops[4].new_state; + blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob); + blob->length = MAX_COLOR_LUT_ENTRIES * sizeof(struct drm_color_lut32); + blob->data = kunit_kzalloc(test, blob->length, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, blob->data); + blend_lut_state->data = blob; + + ret = amdgpu_dm_update_plane_color_mgmt(f.crtc_state, plane_state, + f.dc_plane_state); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, f.dc_plane_state->cm.flags.bits.blend_enable); + KUNIT_EXPECT_EQ(test, (int)f.dc_plane_state->cm.blend_func.type, + (int)TF_TYPE_DISTRIBUTED_POINTS); +} + static struct kunit_case dm_color_test_cases[] = { /* amdgpu_dm_fixpt_from_s3132 */ KUNIT_CASE(dm_test_fixpt_from_s3132_zero), @@ -2819,6 +3021,10 @@ static struct kunit_case dm_color_test_cases[] = { KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_missing_3x4), KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_truncated), KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_no_3dlut_hw), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_shaper), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_3dlut), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_3dlut_no_data), + KUNIT_CASE(dm_test_update_plane_color_mgmt_colorop_blend), {} }; -- cgit v1.2.3 From 354bfcbd0591bf3691ff2b4c0b9c00681e5c38bf Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 14 Jul 2026 10:03:48 -0400 Subject: drm/amd/display: Add KUnit tests for enable_assr Expose enable_assr() for KUnit and cover the "DTM TA not initialized" path and the full command-build path, using the SR-IOV VF early-return to bypass psp_dtm_invoke()'s firmware submit. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 4 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 138 +++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index e704b6b7fc0b..7115cc0c7746 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -570,7 +570,8 @@ void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *hdcp_work) } EXPORT_IF_KUNIT(hdcp_destroy); -static bool enable_assr(void *handle, struct dc_link *link) +STATIC_IFN_KUNIT +bool enable_assr(void *handle, struct dc_link *link) { struct hdcp_workqueue *hdcp_work = handle; struct mod_hdcp hdcp = hdcp_work->hdcp; @@ -603,6 +604,7 @@ static bool enable_assr(void *handle, struct dc_link *link) return true; } +EXPORT_IF_KUNIT(enable_assr); static void update_config(void *handle, struct cp_psp_stream_config *config) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index a2b08bec58b5..b8ba2888a2ef 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -123,6 +123,7 @@ void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_ind struct amdgpu_dm_connector *aconnector); uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size); int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version); +bool enable_assr(void *handle, struct dc_link *link); #endif #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index 5eace7fa4100..1580562727cd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -14,6 +14,8 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_hdcp.h" +#include "amdgpu_dm_kunit_test_helpers.h" +#include "hdcp_psp.h" static void dummy_work_fn(struct work_struct *work) {} @@ -1446,6 +1448,138 @@ static void dm_test_hdcp_reset_display_clears_all_state(struct kunit *test) * hdcp_reset_display(). */ +/* Tests for enable_assr() */ + +/** + * alloc_test_workqueue_for_assr - workqueue wired to a psp for enable_assr() + * @test: KUnit test context for managed allocation + * @adev: amdgpu device whose drm_device backs psp->adev (for drm_info()) + * + * Allocates a minimal hdcp_workqueue and a psp_context connected through + * hdcp.config.psp.handle, matching the dereference chain enable_assr() + * performs. The psp is left with dtm_context.context.initialized == false + * (from kzalloc) so enable_assr() takes the "DTM TA not initialized" path. + */ +static struct hdcp_workqueue *alloc_test_workqueue_for_assr(struct kunit *test, + struct amdgpu_device *adev) +{ + struct hdcp_workqueue *work; + struct psp_context *psp; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + + psp->adev = adev; + work->hdcp.config.psp.handle = psp; + + return work; +} + +/** + * dm_test_enable_assr_uninitialized_dtm_returns_false - DTM TA not initialized + * @test: KUnit test context + * + * When the DTM TA context is not initialized, enable_assr() must take the + * early-return path, emit the informational message and return false + * without invoking the (real) firmware path. + */ +static void dm_test_enable_assr_uninitialized_dtm_returns_false(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct hdcp_workqueue *work = alloc_test_workqueue_for_assr(test, adev); + struct dc_link *link; + bool ret; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* kzalloc leaves dtm_context.context.initialized == false */ + ret = enable_assr(work, link); + + KUNIT_EXPECT_FALSE(test, ret); +} + +/** + * dm_test_enable_assr_uninitialized_dtm_ignores_link - link untouched on failure + * @test: KUnit test context + * + * On the "DTM TA not initialized" path enable_assr() returns before reading + * any field of @link, so a NULL link must be tolerated and the call must + * still return false. + */ +static void dm_test_enable_assr_uninitialized_dtm_ignores_link(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct hdcp_workqueue *work = alloc_test_workqueue_for_assr(test, adev); + bool ret; + + /* link is not dereferenced before the initialized check. */ + ret = enable_assr(work, NULL); + + KUNIT_EXPECT_FALSE(test, ret); +} + +/** + * dm_test_enable_assr_initialized_builds_command_and_fails - full body, invoke bypassed + * @test: KUnit test context + * + * With the DTM TA marked initialized and a valid shared buffer, enable_assr() + * runs its full body: it acquires the DTM mutex, clears the shared command, + * fills in the ASSR-enable command from @link and pre-sets the status to + * GENERIC_FAILURE before invoking the TA. + * + * psp_dtm_invoke() is prevented from touching real firmware by marking the + * device as an SR-IOV virtual function, which makes it return early without + * modifying the shared status. The status therefore stays GENERIC_FAILURE, so + * enable_assr() must return false. Inspecting the shared command afterwards + * proves the body executed and consumed @link. + */ +static void dm_test_enable_assr_initialized_builds_command_and_fails(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct hdcp_workqueue *work = alloc_test_workqueue_for_assr(test, adev); + struct psp_context *psp = work->hdcp.config.psp.handle; + struct ta_dtm_shared_memory *dtm_cmd; + struct dc_link *link; + bool ret; + + dtm_cmd = kunit_kzalloc(test, sizeof(*dtm_cmd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dtm_cmd); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + link->link_enc_hw_inst = 3; + + /* Wire up an "initialized" DTM TA with a real shared buffer. */ + psp->dtm_context.context.initialized = true; + psp->dtm_context.context.mem_context.shared_buf = (uint8_t *)dtm_cmd; + mutex_init(&psp->dtm_context.mutex); + + /* + * Force the SR-IOV VF early-return in psp_dtm_invoke() so no GPU + * command is submitted; the shared status is left untouched. + */ + adev->virt.caps |= AMDGPU_SRIOV_CAPS_IS_VF; + + ret = enable_assr(work, link); + + /* Status was never advanced to SUCCESS, so the call must fail. */ + KUNIT_EXPECT_FALSE(test, ret); + /* The command body must have populated the shared buffer. */ + KUNIT_EXPECT_EQ(test, dtm_cmd->cmd_id, TA_DTM_COMMAND__TOPOLOGY_ASSR_ENABLE); + KUNIT_EXPECT_EQ(test, + dtm_cmd->dtm_in_message.topology_assr_enable.display_topology_dig_be_index, + link->link_enc_hw_inst); + KUNIT_EXPECT_EQ(test, dtm_cmd->dtm_status, TA_DTM_STATUS__GENERIC_FAILURE); + /* The DTM mutex must be released after the guard scope exits. */ + KUNIT_EXPECT_FALSE(test, mutex_is_locked(&psp->dtm_context.mutex)); +} + +/* End of tests for enable_assr() */ + static struct kunit_case dm_hdcp_test_cases[] = { /* hdcp_get_content_protection_from_status() */ KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired), @@ -1507,6 +1641,10 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_hdcp_remove_display_enabled_resets_cp), KUNIT_CASE(dm_test_hdcp_remove_display_null_state_clears_connector), KUNIT_CASE(dm_test_hdcp_reset_display_clears_all_state), + /* enable_assr() */ + KUNIT_CASE(dm_test_enable_assr_uninitialized_dtm_returns_false), + KUNIT_CASE(dm_test_enable_assr_uninitialized_dtm_ignores_link), + KUNIT_CASE(dm_test_enable_assr_initialized_builds_command_and_fails), {} }; -- cgit v1.2.3 From e1460b1047c3cc4716af0dba8753b0c0bc52f93d Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 14 Jul 2026 10:34:20 -0400 Subject: drm/amd/display: Add KUnit tests for update_config Expose update_config() for KUnit and cover the NULL connector and NULL dc_link early returns, the dpms_off removal path and the active path that builds the display/link state and registers the connector. The active path leaves the DTM TA uninitialized so add_display_to_topology() returns early without touching firmware. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 4 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 144 +++++++++++++++++++++ 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 7115cc0c7746..560bfa43dc8a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -606,7 +606,8 @@ bool enable_assr(void *handle, struct dc_link *link) } EXPORT_IF_KUNIT(enable_assr); -static void update_config(void *handle, struct cp_psp_stream_config *config) +STATIC_IFN_KUNIT +void update_config(void *handle, struct cp_psp_stream_config *config) { struct hdcp_workqueue *hdcp_work = handle; struct amdgpu_dm_connector *aconnector; @@ -692,6 +693,7 @@ static void update_config(void *handle, struct cp_psp_stream_config *config) hdcp_w->aconnector[conn_index] = aconnector; process_output(hdcp_w); } +EXPORT_IF_KUNIT(update_config); /** * DOC: Add sysfs interface for set/get srm diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index b8ba2888a2ef..30c1e7b1af1a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -124,6 +124,7 @@ void hdcp_remove_display(struct hdcp_workqueue *hdcp_work, unsigned int link_ind uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size); int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version); bool enable_assr(void *handle, struct dc_link *link); +void update_config(void *handle, struct cp_psp_stream_config *config); #endif #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index 1580562727cd..b75fadd4e14e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -1580,6 +1580,145 @@ static void dm_test_enable_assr_initialized_builds_command_and_fails(struct kuni /* End of tests for enable_assr() */ +/* Tests for update_config() */ + +/** + * dm_test_update_config_null_connector_is_noop - NULL stream ctx returns early + * @test: KUnit test context + * + * When config->dm_stream_ctx is NULL, update_config() must return before + * touching the workqueue, leaving the per-link aconnector array untouched. + */ +static void dm_test_update_config_null_connector_is_noop(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct cp_psp_stream_config config = {0}; + + config.dm_stream_ctx = NULL; + + update_config(work, &config); + + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL); +} + +/** + * dm_test_update_config_null_dc_link_is_noop - NULL dc_link returns early + * @test: KUnit test context + * + * A connector without a dc_link must cause update_config() to return before + * registering the connector, leaving the aconnector array untouched. + */ +static void dm_test_update_config_null_dc_link_is_noop(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector; + struct cp_psp_stream_config config = {0}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aconnector->dc_link = NULL; + + config.dm_stream_ctx = aconnector; + + update_config(work, &config); + + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL); +} + +/** + * dm_test_update_config_dpms_off_removes_display - dpms_off path removes display + * @test: KUnit test context + * + * With config->dpms_off set, update_config() must take the removal path: + * hdcp_remove_display() reverts an ENABLED connector to DESIRED and clears + * its per-link aconnector entry. + */ +static void dm_test_update_config_dpms_off_removes_display(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + struct drm_connector_state *conn_state; + struct cp_psp_stream_config config = {0}; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + aconnector->base.state = conn_state; + work->aconnector[0] = aconnector; + + config.dm_stream_ctx = aconnector; + config.dpms_off = true; + + update_config(work, &config); + + KUNIT_EXPECT_EQ(test, conn_state->content_protection, + DRM_MODE_CONTENT_PROTECTION_DESIRED); + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], NULL); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/** + * dm_test_update_config_populates_display_and_link - active path fills state + * @test: KUnit test context + * + * With dpms_off clear, update_config() must build the display and link from + * @config, reset the connector's encryption_status to HDCP_OFF, register the + * connector and reach process_output() (which enqueues property_validate). + * + * mod_hdcp_add_display() reaches add_display_to_topology(), which returns + * early because the DTM TA is left uninitialized, so no firmware is touched. + */ +static void dm_test_update_config_populates_display_and_link(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_locked(test); + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + struct psp_context *psp; + struct cp_psp_stream_config config = {0}; + + /* add_display_to_topology() dereferences the psp handle. */ + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + work->hdcp.config.psp.handle = psp; + + config.dm_stream_ctx = aconnector; + config.dpms_off = false; + config.otg_inst = 1; + config.dig_fe = 4; + config.dig_be = 5; + config.stream_enc_idx = 6; + config.link_enc_idx = 7; + config.dio_output_idx = 8; + config.phy_idx = 2; + + update_config(work, &config); + + KUNIT_EXPECT_EQ(test, work->encryption_status[0], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); + KUNIT_EXPECT_PTR_EQ(test, work->aconnector[0], aconnector); + + KUNIT_EXPECT_EQ(test, work->display.state, MOD_HDCP_DISPLAY_ACTIVE); + KUNIT_EXPECT_EQ(test, work->display.controller, + CONTROLLER_ID_D0 + config.otg_inst); + KUNIT_EXPECT_EQ(test, work->display.dig_fe, config.dig_fe); + KUNIT_EXPECT_EQ(test, work->display.stream_enc_idx, config.stream_enc_idx); + + KUNIT_EXPECT_EQ(test, work->link.dig_be, config.dig_be); + KUNIT_EXPECT_EQ(test, work->link.link_enc_idx, config.link_enc_idx); + KUNIT_EXPECT_EQ(test, work->link.dio_output_id, config.dio_output_idx); + KUNIT_EXPECT_EQ(test, work->link.phy_idx, config.phy_idx); + + KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work)); + + cancel_delayed_work_sync(&work->property_validate_dwork); + cancel_delayed_work_sync(&work->callback_dwork); + cancel_delayed_work_sync(&work->watchdog_timer_dwork); +} + +/* End of tests for update_config() */ + static struct kunit_case dm_hdcp_test_cases[] = { /* hdcp_get_content_protection_from_status() */ KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired), @@ -1645,6 +1784,11 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_enable_assr_uninitialized_dtm_returns_false), KUNIT_CASE(dm_test_enable_assr_uninitialized_dtm_ignores_link), KUNIT_CASE(dm_test_enable_assr_initialized_builds_command_and_fails), + /* update_config() */ + KUNIT_CASE(dm_test_update_config_null_connector_is_noop), + KUNIT_CASE(dm_test_update_config_null_dc_link_is_noop), + KUNIT_CASE(dm_test_update_config_dpms_off_removes_display), + KUNIT_CASE(dm_test_update_config_populates_display_and_link), {} }; -- cgit v1.2.3 From 13cd064e6831ad145f99e350b08b8615d2103945 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 14 Jul 2026 11:37:13 -0400 Subject: drm/amd/display: Add KUnit tests for hdcp_create_workqueue Cover the success path of hdcp_create_workqueue(): the workqueue and SRM buffers are allocated, max_link is recorded, the cp_psp callbacks and handle are published and every link's psp handle points at the device psp. Also cover the dtm_v3_supported branch (set for DCN 3.1, clear otherwise) and the init loop running for more than one link, alongside the existing zero-link early-return case. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index b75fadd4e14e..d95babb73711 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -1010,6 +1010,106 @@ static void dm_test_hdcp_create_workqueue_zero_max_links_returns_null(struct kun KUNIT_EXPECT_PTR_EQ(test, work, NULL); } +/** + * dm_test_hdcp_create_workqueue_initializes_work - success path wires everything up + * @test: KUnit test context + * + * With a single link, hdcp_create_workqueue() must allocate the workqueue and + * both SRM buffers, record max_link, publish the cp_psp callbacks and handle, + * and point every link's psp handle at the device psp. A non-matching + * dce_version must leave dtm_v3_supported clear. hdcp_destroy() is used to + * tear the workqueue down (it also removes the SRM sysfs file created on the + * device kobject). + */ +static void dm_test_hdcp_create_workqueue_initializes_work(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct cp_psp cp_psp = {0}; + struct dc *dc = dm_kunit_alloc_dc_with_ctx(test); + struct hdcp_workqueue *work; + + /* sysfs_create_bin_file() needs a real device kobject. */ + adev->dev = adev->ddev.dev; + dc->caps.max_links = 1; + /* DCE_VERSION_UNKNOWN (0) is not in the DTM v3 list. */ + dc->ctx->dce_version = DCE_VERSION_UNKNOWN; + + work = hdcp_create_workqueue(adev, &cp_psp, dc); + + KUNIT_ASSERT_NOT_NULL(test, work); + KUNIT_EXPECT_EQ(test, work->max_link, 1); + KUNIT_EXPECT_NOT_NULL(test, work->srm); + KUNIT_EXPECT_NOT_NULL(test, work->srm_temp); + KUNIT_EXPECT_PTR_EQ(test, (void *)cp_psp.funcs.update_stream_config, + (void *)update_config); + KUNIT_EXPECT_PTR_EQ(test, (void *)cp_psp.funcs.enable_assr, + (void *)enable_assr); + KUNIT_EXPECT_PTR_EQ(test, cp_psp.handle, work); + KUNIT_EXPECT_PTR_EQ(test, work[0].hdcp.config.psp.handle, &adev->psp); + KUNIT_EXPECT_EQ(test, work[0].hdcp.config.psp.caps.dtm_v3_supported, 0); + + hdcp_destroy(&adev->dev->kobj, work); +} + +/** + * dm_test_hdcp_create_workqueue_sets_dtm_v3_for_dcn31 - DCN 3.1 enables DTM v3 + * @test: KUnit test context + * + * On a DCN 3.1 device, hdcp_create_workqueue() must set the per-link + * dtm_v3_supported cap; an unmatched dce_version must leave it clear. + */ +static void dm_test_hdcp_create_workqueue_sets_dtm_v3_for_dcn31(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct cp_psp cp_psp = {0}; + struct dc *dc = dm_kunit_alloc_dc_with_ctx(test); + struct hdcp_workqueue *work; + + adev->dev = adev->ddev.dev; + dc->caps.max_links = 1; + dc->ctx->dce_version = DCN_VERSION_3_1; + + work = hdcp_create_workqueue(adev, &cp_psp, dc); + + KUNIT_ASSERT_NOT_NULL(test, work); + KUNIT_EXPECT_EQ(test, work[0].hdcp.config.psp.caps.dtm_v3_supported, 1); + + hdcp_destroy(&adev->dev->kobj, work); +} + +/** + * dm_test_hdcp_create_workqueue_initializes_all_links - loop covers every link + * @test: KUnit test context + * + * With more than one link, hdcp_create_workqueue() must run its init loop for + * every entry: each link records max_link, points its psp handle at the device + * psp and gets the DTM v3 cap set for a matching dce_version. + */ +static void dm_test_hdcp_create_workqueue_initializes_all_links(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct cp_psp cp_psp = {0}; + struct dc *dc = dm_kunit_alloc_dc_with_ctx(test); + struct hdcp_workqueue *work; + int i; + + adev->dev = adev->ddev.dev; + dc->caps.max_links = 3; + dc->ctx->dce_version = DCN_VERSION_3_1; + + work = hdcp_create_workqueue(adev, &cp_psp, dc); + + KUNIT_ASSERT_NOT_NULL(test, work); + KUNIT_EXPECT_EQ(test, work->max_link, 3); + for (i = 0; i < 3; i++) { + KUNIT_EXPECT_PTR_EQ(test, work[i].hdcp.config.psp.handle, + &adev->psp); + KUNIT_EXPECT_EQ(test, work[i].hdcp.config.psp.caps.dtm_v3_supported, 1); + } + + hdcp_destroy(&adev->dev->kobj, work); +} + /* End of tests for hdcp_create_workqueue() */ /* Tests for hdcp_destroy() */ @@ -1765,6 +1865,9 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_hdcp_update_display_disable_resets_status_and_cancels_validate), /* hdcp_create_workqueue() */ KUNIT_CASE(dm_test_hdcp_create_workqueue_zero_max_links_returns_null), + KUNIT_CASE(dm_test_hdcp_create_workqueue_initializes_work), + KUNIT_CASE(dm_test_hdcp_create_workqueue_sets_dtm_v3_for_dcn31), + KUNIT_CASE(dm_test_hdcp_create_workqueue_initializes_all_links), /* hdcp_destroy() */ KUNIT_CASE(dm_test_hdcp_destroy_frees_and_removes_sysfs), KUNIT_CASE(dm_test_hdcp_destroy_zero_links_null_srm), -- cgit v1.2.3 From eb5e911dff1862c462f2ca1e0ba3ebdd8bb0e605 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 14 Jul 2026 11:50:12 -0400 Subject: drm/amd/display: Add KUnit tests for srm_data_write and srm_data_read Expose srm_data_write() and srm_data_read() for KUnit and add tests covering the sysfs SRM write/read paths: - write stages the buffer into srm_temp and returns count; with the TA uninitialized psp_set_srm() fails so the committed SRM is unchanged - read returns -EINVAL when psp_get_srm() returns NULL (TA uninitialized) - read returns 0 for an empty SRM using the SR-IOV VF bypass so psp_hdcp_invoke() is a no-op and psp_get_srm() returns a non-NULL buffer Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 6 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 9 ++ .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 117 +++++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 560bfa43dc8a..9ea0a120af6f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -743,7 +743,7 @@ EXPORT_IF_KUNIT(update_config); * -if we try to "1. SET" a newer version and PSP rejects it. That means the format is * incorrect/corrupted and we should correct our SRM by getting it from PSP */ -static ssize_t srm_data_write(struct file *filp, struct kobject *kobj, +STATIC_IFN_KUNIT ssize_t srm_data_write(struct file *filp, struct kobject *kobj, const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t count) { @@ -766,8 +766,9 @@ static ssize_t srm_data_write(struct file *filp, struct kobject *kobj, return count; } +EXPORT_IF_KUNIT(srm_data_write); -static ssize_t srm_data_read(struct file *filp, struct kobject *kobj, +STATIC_IFN_KUNIT ssize_t srm_data_read(struct file *filp, struct kobject *kobj, const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t count) { @@ -803,6 +804,7 @@ ret: link_lock(work, false); return ret; } +EXPORT_IF_KUNIT(srm_data_read); /* From the hdcp spec (5.Renewability) SRM needs to be stored in a non-volatile memory. * diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index 30c1e7b1af1a..e700d019f971 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -45,6 +45,9 @@ struct mod_hdcp_display; struct cp_psp; struct amdgpu_device; struct psp_context; +struct file; +struct kobject; +struct bin_attribute; struct hdcp_workqueue { struct work_struct cpirq_work; @@ -125,6 +128,12 @@ uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *s int psp_set_srm(struct psp_context *psp, u8 *srm, uint32_t srm_size, uint32_t *srm_version); bool enable_assr(void *handle, struct dc_link *link); void update_config(void *handle, struct cp_psp_stream_config *config); +ssize_t srm_data_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buffer, + loff_t pos, size_t count); +ssize_t srm_data_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buffer, + loff_t pos, size_t count); #endif #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index d95babb73711..a8bca05f47a3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -1348,6 +1348,119 @@ static void dm_test_psp_set_srm_uninitialized_returns_einval(struct kunit *test) /* End of tests for psp_get_srm() and psp_set_srm() */ +/* Tests for srm_data_write() and srm_data_read() */ + +/** + * dm_test_srm_data_write_uninitialized_ta_keeps_srm - write with TA not initialized + * @test: KUnit test context + * + * srm_data_write() always copies the incoming buffer into work->srm_temp and + * returns the byte count. When the HDCP TA is not initialized, psp_set_srm() + * fails, so the committed SRM (work->srm / work->srm_size) must stay untouched. + */ +static void dm_test_srm_data_write_uninitialized_ta_keeps_srm(struct kunit *test) +{ + struct hdcp_workqueue *work; + struct psp_context *psp; + u8 buf[4] = {0xAA, 0xBB, 0xCC, 0xDD}; + ssize_t ret; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + + work->max_link = 1; + mutex_init(&work->mutex); + /* kzalloc leaves hdcp_context.context.initialized == false */ + work->hdcp.config.psp.handle = psp; + work->srm_temp = kunit_kzalloc(test, PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work->srm_temp); + work->srm = kunit_kzalloc(test, PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work->srm); + + ret = srm_data_write(NULL, NULL, &work->attr, buf, 0, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buf)); + /* Incoming data is always staged into srm_temp. */ + KUNIT_EXPECT_MEMEQ(test, work->srm_temp, buf, sizeof(buf)); + /* psp_set_srm() failed, so the committed SRM must be unchanged. */ + KUNIT_EXPECT_EQ(test, work->srm_size, 0u); +} + +/** + * dm_test_srm_data_read_uninitialized_ta_returns_einval - read with TA not initialized + * @test: KUnit test context + * + * When the HDCP TA is not initialized, psp_get_srm() returns NULL, so + * srm_data_read() must take the error path and return -EINVAL. + */ +static void dm_test_srm_data_read_uninitialized_ta_returns_einval(struct kunit *test) +{ + struct hdcp_workqueue *work; + struct psp_context *psp; + u8 buf[4]; + ssize_t ret; + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + + work->max_link = 1; + mutex_init(&work->mutex); + /* kzalloc leaves hdcp_context.context.initialized == false */ + work->hdcp.config.psp.handle = psp; + + ret = srm_data_read(NULL, NULL, &work->attr, buf, 0, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)-EINVAL); +} + +/** + * dm_test_srm_data_read_empty_srm_returns_zero - read of an empty SRM + * @test: KUnit test context + * + * With an initialized TA and the SR-IOV VF bypass, psp_hdcp_invoke() is a + * no-op and the zeroed shared buffer yields a SUCCESS status with srm_size 0. + * psp_get_srm() then returns a non-NULL (empty) buffer, so srm_data_read() + * takes the "nothing left to copy" path and returns 0. + */ +static void dm_test_srm_data_read_empty_srm_returns_zero(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct ta_hdcp_shared_memory *hdcp_cmd; + struct hdcp_workqueue *work; + struct psp_context *psp; + u8 buf[4]; + ssize_t ret; + + KUNIT_ASSERT_NOT_NULL(test, adev); + + work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, work); + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + hdcp_cmd = kunit_kzalloc(test, sizeof(*hdcp_cmd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdcp_cmd); + + work->max_link = 1; + mutex_init(&work->mutex); + psp->adev = adev; + psp->hdcp_context.context.initialized = true; + psp->hdcp_context.context.mem_context.shared_buf = (uint8_t *)hdcp_cmd; + work->hdcp.config.psp.handle = psp; + + /* SR-IOV VF makes psp_hdcp_invoke() return early without firmware. */ + adev->virt.caps |= AMDGPU_SRIOV_CAPS_IS_VF; + + ret = srm_data_read(NULL, NULL, &work->attr, buf, 0, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/* End of tests for srm_data_write() and srm_data_read() */ + /* * Tests for hdcp_update_display() / hdcp_remove_display() / * hdcp_reset_display(). @@ -1877,6 +1990,10 @@ static struct kunit_case dm_hdcp_test_cases[] = { /* psp_get_srm() / psp_set_srm() */ KUNIT_CASE(dm_test_psp_get_srm_uninitialized_returns_null), KUNIT_CASE(dm_test_psp_set_srm_uninitialized_returns_einval), + /* srm_data_write() / srm_data_read() */ + KUNIT_CASE(dm_test_srm_data_write_uninitialized_ta_keeps_srm), + KUNIT_CASE(dm_test_srm_data_read_uninitialized_ta_returns_einval), + KUNIT_CASE(dm_test_srm_data_read_empty_srm_returns_zero), /* hdcp_update_display() / hdcp_remove_display() / hdcp_reset_display() */ KUNIT_CASE(dm_test_hdcp_update_display_enable_registers_connector), KUNIT_CASE(dm_test_hdcp_update_display_disable_sets_status_off), -- cgit v1.2.3 From 249401c325609c394ab4b126f64c04743dc87373 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 14 Jul 2026 12:37:25 -0400 Subject: drm/amd/display: Add KUnit tests for HDCP DDC link adapters Expose the mod_hdcp DDC adapter callbacks for KUnit and add tests: lp_write_i2c / lp_read_i2c / lp_write_dpcd / lp_read_dpcd use recording fake i2c and DP aux backends to assert the built payloads (write flag, address, length, buffer, and the read offset-then-data sequence), plus failure when the connector is missing. lp_atomic_write_poll_read_i2c / lp_atomic_write_poll_read_aux cover the hardware-free early returns (NULL link and a payload too large to convert); the success path submits fused-IO to the DMCUB and is out of reach for a unit test. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 50 +-- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 16 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 378 +++++++++++++++++++++ 3 files changed, 420 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 9ea0a120af6f..c887a70ae74d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -38,8 +38,7 @@ */ #define PSP_SRM_VERSION_MAX 0xFFFF -static bool -lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size) +STATIC_IFN_KUNIT bool lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size) { struct dc_link *link = handle; struct i2c_payload i2c_payloads[] = {{true, address, size, (void *)data} }; @@ -48,9 +47,9 @@ lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size) return dm_helpers_submit_i2c(link->ctx, link, &cmd); } +EXPORT_IF_KUNIT(lp_write_i2c); -static bool -lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint32_t size) +STATIC_IFN_KUNIT bool lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint32_t size) { struct dc_link *link = handle; @@ -61,50 +60,53 @@ lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint3 return dm_helpers_submit_i2c(link->ctx, link, &cmd); } +EXPORT_IF_KUNIT(lp_read_i2c); -static bool -lp_write_dpcd(void *handle, uint32_t address, const uint8_t *data, uint32_t size) +STATIC_IFN_KUNIT bool lp_write_dpcd(void *handle, uint32_t address, const uint8_t *data, uint32_t size) { struct dc_link *link = handle; return dm_helpers_dp_write_dpcd(link->ctx, link, address, data, size); } +EXPORT_IF_KUNIT(lp_write_dpcd); -static bool -lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size) +STATIC_IFN_KUNIT bool lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size) { struct dc_link *link = handle; return dm_helpers_dp_read_dpcd(link->ctx, link, address, data, size); } - -static bool lp_atomic_write_poll_read_i2c( - void *handle, - const struct mod_hdcp_atomic_op_i2c *write, - const struct mod_hdcp_atomic_op_i2c *poll, - struct mod_hdcp_atomic_op_i2c *read, - uint32_t poll_timeout_us, - uint8_t poll_mask_msb +EXPORT_IF_KUNIT(lp_read_dpcd); + +STATIC_IFN_KUNIT bool lp_atomic_write_poll_read_i2c( + void *handle, + const struct mod_hdcp_atomic_op_i2c *write, + const struct mod_hdcp_atomic_op_i2c *poll, + struct mod_hdcp_atomic_op_i2c *read, + uint32_t poll_timeout_us, + uint8_t poll_mask_msb ) { struct dc_link *link = handle; return dm_atomic_write_poll_read_i2c(link, write, poll, read, poll_timeout_us, poll_mask_msb); } - -static bool lp_atomic_write_poll_read_aux( - void *handle, - const struct mod_hdcp_atomic_op_aux *write, - const struct mod_hdcp_atomic_op_aux *poll, - struct mod_hdcp_atomic_op_aux *read, - uint32_t poll_timeout_us, - uint8_t poll_mask_msb +EXPORT_IF_KUNIT(lp_atomic_write_poll_read_i2c); + +STATIC_IFN_KUNIT bool lp_atomic_write_poll_read_aux( + void *handle, + const struct mod_hdcp_atomic_op_aux *write, + const struct mod_hdcp_atomic_op_aux *poll, + struct mod_hdcp_atomic_op_aux *read, + uint32_t poll_timeout_us, + uint8_t poll_mask_msb ) { struct dc_link *link = handle; return dm_atomic_write_poll_read_aux(link, write, poll, read, poll_timeout_us, poll_mask_msb); } +EXPORT_IF_KUNIT(lp_atomic_write_poll_read_aux); STATIC_IFN_KUNIT uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index e700d019f971..665fa4839a25 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -48,6 +48,8 @@ struct psp_context; struct file; struct kobject; struct bin_attribute; +struct mod_hdcp_atomic_op_i2c; +struct mod_hdcp_atomic_op_aux; struct hdcp_workqueue { struct work_struct cpirq_work; @@ -134,6 +136,20 @@ ssize_t srm_data_write(struct file *filp, struct kobject *kobj, ssize_t srm_data_read(struct file *filp, struct kobject *kobj, const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t count); +bool lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size); +bool lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint32_t size); +bool lp_write_dpcd(void *handle, uint32_t address, const uint8_t *data, uint32_t size); +bool lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size); +bool lp_atomic_write_poll_read_i2c(void *handle, + const struct mod_hdcp_atomic_op_i2c *write, + const struct mod_hdcp_atomic_op_i2c *poll, + struct mod_hdcp_atomic_op_i2c *read, + uint32_t poll_timeout_us, uint8_t poll_mask_msb); +bool lp_atomic_write_poll_read_aux(void *handle, + const struct mod_hdcp_atomic_op_aux *write, + const struct mod_hdcp_atomic_op_aux *poll, + struct mod_hdcp_atomic_op_aux *read, + uint32_t poll_timeout_us, uint8_t poll_mask_msb); #endif #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index a8bca05f47a3..838826d274c2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -10,6 +10,9 @@ #include #include #include +#include + +#include #include "amdgpu.h" #include "amdgpu_dm.h" @@ -1461,6 +1464,369 @@ static void dm_test_srm_data_read_empty_srm_returns_zero(struct kunit *test) /* End of tests for srm_data_write() and srm_data_read() */ +/* Tests for lp_write_i2c() / lp_read_i2c() / lp_write_dpcd() / lp_read_dpcd() */ + +/* Defined further below with the display helper tests. */ +static struct amdgpu_dm_connector *alloc_test_connector(struct kunit *test, + unsigned int index); + +/* + * Recording fakes for the DDC layer. The lp_* wrappers build i2c/DPCD + * transactions and forward them through dm_helpers_*, which end up calling + * i2c_transfer() / drm_dp_dpcd_*(). These fakes capture the resulting + * messages so the tests can assert what the wrappers built, without touching + * real hardware. KUnit runs cases sequentially, so file-scope capture state + * is reset at the start of each test. + */ +#define FAKE_DDC_MAX_MSGS 4 + +static struct fake_i2c_capture { + int num; + struct i2c_msg msgs[FAKE_DDC_MAX_MSGS]; +} fake_i2c_cap; + +static int fake_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + int i; + + fake_i2c_cap.num = num; + for (i = 0; i < num && i < FAKE_DDC_MAX_MSGS; i++) + fake_i2c_cap.msgs[i] = msgs[i]; + + return num; +} + +static u32 fake_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm fake_i2c_algo = { + .master_xfer = fake_i2c_master_xfer, + .functionality = fake_i2c_functionality, +}; + +static void fake_i2c_lock_bus(struct i2c_adapter *adap, unsigned int flags) {} +static int fake_i2c_trylock_bus(struct i2c_adapter *adap, unsigned int flags) +{ + return 1; +} +static void fake_i2c_unlock_bus(struct i2c_adapter *adap, unsigned int flags) {} + +static const struct i2c_lock_operations fake_i2c_lock_ops = { + .lock_bus = fake_i2c_lock_bus, + .trylock_bus = fake_i2c_trylock_bus, + .unlock_bus = fake_i2c_unlock_bus, +}; + +static struct fake_aux_capture { + int calls; + u8 request; + unsigned int address; + size_t size; +} fake_aux_cap; + +static ssize_t fake_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + fake_aux_cap.calls++; + fake_aux_cap.request = msg->request; + fake_aux_cap.address = msg->address; + fake_aux_cap.size = msg->size; + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + + return msg->size; +} + +/** + * alloc_test_ddc_link - connector/link wired to the recording i2c + aux fakes + * @test: KUnit test context for managed allocation + * + * Builds an amdgpu_dm_connector with a fake i2c adapter and a fake DP aux, and + * points link->priv at the connector so dm_helpers_* find it. Returns the + * dc_link that the lp_* wrappers take as their opaque handle. + */ +static struct dc_link *alloc_test_ddc_link(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + struct amdgpu_i2c_adapter *i2c; + struct dc_link *link; + + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + i2c = kunit_kzalloc(test, sizeof(*i2c), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, i2c); + i2c->base.algo = &fake_i2c_algo; + i2c->base.lock_ops = &fake_i2c_lock_ops; + aconnector->i2c = i2c; + + mutex_init(&aconnector->dm_dp_aux.aux.hw_mutex); + aconnector->dm_dp_aux.aux.transfer = fake_aux_transfer; + /* Skip the DPCD "throw away" probe read so we capture only our access. */ + aconnector->dm_dp_aux.aux.dpcd_probe_disabled = true; + + link = aconnector->dc_link; + link->priv = aconnector; + + return link; +} + +/** + * dm_test_lp_write_i2c_builds_single_write_payload - write builds one i2c msg + * @test: KUnit test context + * + * lp_write_i2c() must forward a single write payload carrying the address, + * length and data buffer unchanged. + */ +static void dm_test_lp_write_i2c_builds_single_write_payload(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[3] = {0x11, 0x22, 0x33}; + bool ok; + + memset(&fake_i2c_cap, 0, sizeof(fake_i2c_cap)); + + ok = lp_write_i2c(link, 0x3a, data, sizeof(data)); + + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_ASSERT_EQ(test, fake_i2c_cap.num, 1); + /* write => flags without I2C_M_RD */ + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].flags, 0); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].addr, 0x3a); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].len, (int)sizeof(data)); + KUNIT_EXPECT_PTR_EQ(test, fake_i2c_cap.msgs[0].buf, (void *)data); +} + +/** + * dm_test_lp_read_i2c_builds_offset_then_read - read builds offset + read msgs + * @test: KUnit test context + * + * lp_read_i2c() must build a 1-byte write of the offset followed by a + * size-byte read into the caller buffer, both at the same address. + */ +static void dm_test_lp_read_i2c_builds_offset_then_read(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[4]; + bool ok; + + memset(&fake_i2c_cap, 0, sizeof(fake_i2c_cap)); + + ok = lp_read_i2c(link, 0x50, 0x07, data, sizeof(data)); + + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_ASSERT_EQ(test, fake_i2c_cap.num, 2); + /* first: 1-byte write of the offset */ + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].flags, 0); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].addr, 0x50); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[0].len, 1); + /* second: size-byte read into the caller buffer */ + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[1].flags, I2C_M_RD); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[1].addr, 0x50); + KUNIT_EXPECT_EQ(test, (int)fake_i2c_cap.msgs[1].len, (int)sizeof(data)); + KUNIT_EXPECT_PTR_EQ(test, fake_i2c_cap.msgs[1].buf, (void *)data); +} + +/** + * dm_test_lp_write_dpcd_forwards_native_write - write forwards a native write + * @test: KUnit test context + * + * lp_write_dpcd() must issue a single DP_AUX_NATIVE_WRITE at the requested + * address for the requested size. + */ +static void dm_test_lp_write_dpcd_forwards_native_write(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[2] = {0xDE, 0xAD}; + bool ok; + + memset(&fake_aux_cap, 0, sizeof(fake_aux_cap)); + + ok = lp_write_dpcd(link, 0x68000, data, sizeof(data)); + + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, fake_aux_cap.calls, 1); + KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.request, DP_AUX_NATIVE_WRITE); + KUNIT_EXPECT_EQ(test, fake_aux_cap.address, 0x68000u); + KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.size, (int)sizeof(data)); +} + +/** + * dm_test_lp_read_dpcd_forwards_native_read - read forwards a native read + * @test: KUnit test context + * + * lp_read_dpcd() must issue a single DP_AUX_NATIVE_READ at the requested + * address for the requested size. + */ +static void dm_test_lp_read_dpcd_forwards_native_read(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[4]; + bool ok; + + memset(&fake_aux_cap, 0, sizeof(fake_aux_cap)); + + ok = lp_read_dpcd(link, 0x00220, data, sizeof(data)); + + KUNIT_EXPECT_TRUE(test, ok); + KUNIT_EXPECT_EQ(test, fake_aux_cap.calls, 1); + KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.request, DP_AUX_NATIVE_READ); + KUNIT_EXPECT_EQ(test, fake_aux_cap.address, 0x00220u); + KUNIT_EXPECT_EQ(test, (int)fake_aux_cap.size, (int)sizeof(data)); +} + +/** + * dm_test_lp_write_i2c_no_connector_returns_false - missing connector fails + * @test: KUnit test context + * + * When link->priv has no connector, dm_helpers_submit_i2c() cannot proceed, + * so lp_write_i2c() must report failure without invoking the adapter. + */ +static void dm_test_lp_write_i2c_no_connector_returns_false(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[2] = {0x01, 0x02}; + bool ok; + + link->priv = NULL; + memset(&fake_i2c_cap, 0, sizeof(fake_i2c_cap)); + + ok = lp_write_i2c(link, 0x3a, data, sizeof(data)); + + KUNIT_EXPECT_FALSE(test, ok); + KUNIT_EXPECT_EQ(test, fake_i2c_cap.num, 0); +} + +/** + * dm_test_lp_read_dpcd_no_connector_returns_false - missing connector fails + * @test: KUnit test context + * + * When link->priv has no connector, dm_helpers_dp_read_dpcd() cannot proceed, + * so lp_read_dpcd() must report failure without invoking the aux transfer. + */ +static void dm_test_lp_read_dpcd_no_connector_returns_false(struct kunit *test) +{ + struct dc_link *link = alloc_test_ddc_link(test); + u8 data[4]; + bool ok; + + link->priv = NULL; + memset(&fake_aux_cap, 0, sizeof(fake_aux_cap)); + + ok = lp_read_dpcd(link, 0x00220, data, sizeof(data)); + + KUNIT_EXPECT_FALSE(test, ok); + KUNIT_EXPECT_EQ(test, fake_aux_cap.calls, 0); +} + +/* End of tests for lp_write_i2c() / lp_read_i2c() / lp_write_dpcd() / lp_read_dpcd() */ + +/* + * Tests for lp_atomic_write_poll_read_i2c() / lp_atomic_write_poll_read_aux() + * + * These wrappers cast the opaque handle to a dc_link and forward to the + * dc_fused_io helpers. The success path submits a fused-IO command sequence to + * the DMCUB, which is out of reach for a unit test, so the coverage here is the + * hardware-free early returns: a NULL link and a payload that fails conversion + * (op size larger than the fused request buffer). + */ + +/** + * dm_test_lp_atomic_i2c_null_handle_returns_false - NULL link fails cleanly + * @test: KUnit test context + * + * With a NULL handle the forwarded dc_link is NULL, so the helper must return + * false without dereferencing anything. + */ +static void dm_test_lp_atomic_i2c_null_handle_returns_false(struct kunit *test) +{ + struct mod_hdcp_atomic_op_i2c op = { 0 }; + + KUNIT_EXPECT_FALSE(test, + lp_atomic_write_poll_read_i2c(NULL, &op, &op, &op, 0, 0)); +} + +/** + * dm_test_lp_atomic_i2c_oversized_op_returns_false - bad payload fails conversion + * @test: KUnit test context + * + * An op whose size exceeds the fused request buffer must fail conversion, so + * the helper returns false before any fused-IO submission. no_ddc_pin routes + * the DDC line through aux_hw_inst, avoiding the GPIO pin dereference. + */ +static void dm_test_lp_atomic_i2c_oversized_op_returns_false(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + struct mod_hdcp_atomic_op_i2c write = { .size = 0x100 }; + struct mod_hdcp_atomic_op_i2c op = { 0 }; + struct dc_link *link; + + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + link = aconnector->dc_link; + link->no_ddc_pin = true; + + KUNIT_EXPECT_FALSE(test, + lp_atomic_write_poll_read_i2c(link, &write, &op, &op, 0, 0)); +} + +/** + * dm_test_lp_atomic_aux_null_handle_returns_false - NULL link fails cleanly + * @test: KUnit test context + * + * With a NULL handle the forwarded dc_link is NULL, so the helper must return + * false without dereferencing anything. + */ +static void dm_test_lp_atomic_aux_null_handle_returns_false(struct kunit *test) +{ + struct mod_hdcp_atomic_op_aux op = { 0 }; + + KUNIT_EXPECT_FALSE(test, + lp_atomic_write_poll_read_aux(NULL, &op, &op, &op, 0, 0)); +} + +/** + * dm_test_lp_atomic_aux_oversized_op_returns_false - bad payload fails conversion + * @test: KUnit test context + * + * The aux helper reads the DDC line from link->ddc->ddc_pin->pin_data before + * converting, so a minimal pin chain is wired up. An op larger than the fused + * request buffer then fails conversion and the helper returns false without + * any fused-IO submission. + */ +static void dm_test_lp_atomic_aux_oversized_op_returns_false(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = alloc_test_connector(test, 0); + struct mod_hdcp_atomic_op_aux write = { .size = 0x100 }; + struct mod_hdcp_atomic_op_aux op = { 0 }; + struct ddc_service *ddc; + struct ddc *ddc_pin; + struct dc_link *link; + void *pin_data; + + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ddc); + ddc_pin = kunit_kzalloc(test, sizeof(*ddc_pin), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ddc_pin); + /* Only ->en is read; over-allocate so struct gpio stays opaque here. */ + pin_data = kunit_kzalloc(test, 128, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pin_data); + + ddc_pin->pin_data = pin_data; + ddc->ddc_pin = ddc_pin; + + link = aconnector->dc_link; + link->ddc = ddc; + + KUNIT_EXPECT_FALSE(test, + lp_atomic_write_poll_read_aux(link, &write, &op, &op, 0, 0)); +} + +/* End of tests for lp_atomic_write_poll_read_i2c() / lp_atomic_write_poll_read_aux() */ + /* * Tests for hdcp_update_display() / hdcp_remove_display() / * hdcp_reset_display(). @@ -1994,6 +2360,18 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_srm_data_write_uninitialized_ta_keeps_srm), KUNIT_CASE(dm_test_srm_data_read_uninitialized_ta_returns_einval), KUNIT_CASE(dm_test_srm_data_read_empty_srm_returns_zero), + /* lp_write_i2c() / lp_read_i2c() / lp_write_dpcd() / lp_read_dpcd() */ + KUNIT_CASE(dm_test_lp_write_i2c_builds_single_write_payload), + KUNIT_CASE(dm_test_lp_read_i2c_builds_offset_then_read), + KUNIT_CASE(dm_test_lp_write_dpcd_forwards_native_write), + KUNIT_CASE(dm_test_lp_read_dpcd_forwards_native_read), + KUNIT_CASE(dm_test_lp_write_i2c_no_connector_returns_false), + KUNIT_CASE(dm_test_lp_read_dpcd_no_connector_returns_false), + /* lp_atomic_write_poll_read_i2c() / lp_atomic_write_poll_read_aux() */ + KUNIT_CASE(dm_test_lp_atomic_i2c_null_handle_returns_false), + KUNIT_CASE(dm_test_lp_atomic_i2c_oversized_op_returns_false), + KUNIT_CASE(dm_test_lp_atomic_aux_null_handle_returns_false), + KUNIT_CASE(dm_test_lp_atomic_aux_oversized_op_returns_false), /* hdcp_update_display() / hdcp_remove_display() / hdcp_reset_display() */ KUNIT_CASE(dm_test_hdcp_update_display_enable_registers_connector), KUNIT_CASE(dm_test_hdcp_update_display_disable_sets_status_off), -- cgit v1.2.3 From fd269d5a308c003b02ba70cfa5422957008740fe Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 14 Jul 2026 12:59:19 -0400 Subject: drm/amd/display: Add initialized-branch test for psp_set_srm Cover the initialized path of psp_set_srm() using the SR-IOV VF bypass so psp_hdcp_invoke() is a no-op. The test asserts the SET_SRM command is staged (cmd_id, srm_buf_size and the copied SRM bytes) and that response validation fails on the zeroed reply, returning -EINVAL without updating srm_version. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index 838826d274c2..b359ba5d524b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -1349,6 +1349,52 @@ static void dm_test_psp_set_srm_uninitialized_returns_einval(struct kunit *test) KUNIT_EXPECT_EQ(test, srm_version, 0xdead); } +/** + * dm_test_psp_set_srm_initialized_stages_command - initialized path builds the command + * @test: KUnit test context + * + * With an initialized TA and the SR-IOV VF bypass, psp_hdcp_invoke() is a + * no-op, so the shared command buffer keeps the values psp_set_srm() staged. + * The function must copy the SRM into the SET_SRM in-message, record its size + * and command id, and then fail the response validation (the zeroed reply has + * valid_signature == 0), returning -EINVAL without updating srm_version. + */ +static void dm_test_psp_set_srm_initialized_stages_command(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct ta_hdcp_shared_memory *hdcp_cmd; + struct psp_context *psp; + uint32_t srm_version = 0xdead; + u8 srm_buf[4] = {0x1, 0x2, 0x3, 0x4}; + int ret; + + KUNIT_ASSERT_NOT_NULL(test, adev); + + psp = kunit_kzalloc(test, sizeof(*psp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, psp); + hdcp_cmd = kunit_kzalloc(test, sizeof(*hdcp_cmd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdcp_cmd); + + psp->adev = adev; + psp->hdcp_context.context.initialized = true; + psp->hdcp_context.context.mem_context.shared_buf = (uint8_t *)hdcp_cmd; + + /* SR-IOV VF makes psp_hdcp_invoke() return early without firmware. */ + adev->virt.caps |= AMDGPU_SRIOV_CAPS_IS_VF; + + ret = psp_set_srm(psp, srm_buf, sizeof(srm_buf), &srm_version); + + /* Response validation fails (valid_signature == 0 in the zeroed reply). */ + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, srm_version, 0xdead); + /* The initialized path must have staged the SET_SRM command. */ + KUNIT_EXPECT_EQ(test, hdcp_cmd->cmd_id, TA_HDCP_COMMAND__HDCP_SET_SRM); + KUNIT_EXPECT_EQ(test, hdcp_cmd->in_msg.hdcp_set_srm.srm_buf_size, + (uint32_t)sizeof(srm_buf)); + KUNIT_EXPECT_MEMEQ(test, hdcp_cmd->in_msg.hdcp_set_srm.srm_buf, srm_buf, + sizeof(srm_buf)); +} + /* End of tests for psp_get_srm() and psp_set_srm() */ /* Tests for srm_data_write() and srm_data_read() */ @@ -2356,6 +2402,7 @@ static struct kunit_case dm_hdcp_test_cases[] = { /* psp_get_srm() / psp_set_srm() */ KUNIT_CASE(dm_test_psp_get_srm_uninitialized_returns_null), KUNIT_CASE(dm_test_psp_set_srm_uninitialized_returns_einval), + KUNIT_CASE(dm_test_psp_set_srm_initialized_stages_command), /* srm_data_write() / srm_data_read() */ KUNIT_CASE(dm_test_srm_data_write_uninitialized_ta_keeps_srm), KUNIT_CASE(dm_test_srm_data_read_uninitialized_ta_returns_einval), -- cgit v1.2.3 From df1bdfaf57eccfb8ab012184dbce8ca63f78ca5e Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Tue, 14 Jul 2026 13:48:22 -0400 Subject: drm/amd/display: Add deeper event_property_update tests Expand KUnit coverage for event_property_update() beyond the null connector skip. Add cases for the disconnected, missing-state and missing-device skip branches, plus the fully connected path where hdcp_get_content_protection_from_status() maps HDCP_OFF to DESIRED and HDCP1 TYPE0 encryption to ENABLED. The connected-path tests use dm_kunit_alloc_adev() so the drm_device has an initialised mode_config connection_mutex, and pre-set the connector state to the expected content protection value so drm_hdcp_update_content_protection() takes its no-change early return. Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 174 ++++++++++++++++++++- 1 file changed, 173 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index b359ba5d524b..cfdcc44581b1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -473,7 +473,8 @@ static void dm_test_process_output_watchdog_stop_and_needed_requeues(struct kuni * @test: KUnit test context for managed allocation * * Allocates a minimal hdcp_workqueue with property_update_work initialised - * so event_property_update() can resolve container_of() safely. + * so event_property_update() can resolve container_of() safely. The mutex is + * initialised as well because the connected path takes guard(mutex). */ static struct hdcp_workqueue *alloc_test_workqueue_for_property_update(struct kunit *test) { @@ -482,11 +483,41 @@ static struct hdcp_workqueue *alloc_test_workqueue_for_property_update(struct ku work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, work); + mutex_init(&work->mutex); INIT_WORK(&work->property_update_work, dummy_work_fn); return work; } +/** + * alloc_update_connector - connector for event_property_update() tests + * @test: KUnit test context for managed allocation + * @status: drm connector detection status to assign + * @conn_state: drm_connector_state to attach (may be NULL) + * @dev: drm_device to attach as connector->dev (may be NULL) + * + * Allocates an amdgpu_dm_connector wired for the traversal in + * event_property_update(). The caller supplies the state and device so the + * various skip branches (disconnected, no state, no device) and the fully + * connected path can all be exercised. + */ +static struct amdgpu_dm_connector *alloc_update_connector(struct kunit *test, + enum drm_connector_status status, + struct drm_connector_state *conn_state, + struct drm_device *dev) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.status = status; + aconnector->base.state = conn_state; + aconnector->base.dev = dev; + + return aconnector; +} + /** * dm_test_event_property_update_skips_null_connector - null connector is ignored * @test: KUnit test context @@ -507,6 +538,142 @@ static void dm_test_event_property_update_skips_null_connector(struct kunit *tes KUNIT_EXPECT_EQ(test, work->encryption_status[0], before); } +/** + * dm_test_event_property_update_skips_disconnected - disconnected is skipped + * @test: KUnit test context + * + * A connector whose status is not connector_status_connected must be skipped + * before any modeset lock is taken, leaving encryption_status untouched. + */ +static void dm_test_event_property_update_skips_disconnected(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + work->aconnector[1] = alloc_update_connector(test, connector_status_disconnected, + conn_state, NULL); + work->encryption_status[1] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[1], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_update_skips_null_state - missing state is skipped + * @test: KUnit test context + * + * A connected connector without a drm_connector_state must be skipped before + * the modeset lock, leaving encryption_status unchanged. + */ +static void dm_test_event_property_update_skips_null_state(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + + work->aconnector[2] = alloc_update_connector(test, connector_status_connected, + NULL, NULL); + work->encryption_status[2] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[2], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_update_skips_null_dev - missing device is skipped + * @test: KUnit test context + * + * A connected connector with state but no drm_device must be skipped before + * the modeset lock, leaving encryption_status unchanged. + */ +static void dm_test_event_property_update_skips_null_dev(struct kunit *test) +{ + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + work->aconnector[3] = alloc_update_connector(test, connector_status_connected, + conn_state, NULL); + work->encryption_status[3] = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, work->encryption_status[3], + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON); +} + +/** + * dm_test_event_property_update_desired_when_off - HDCP off maps to DESIRED + * @test: KUnit test context + * + * A fully connected display with HDCP_OFF encryption drives the connected + * path: the modeset lock is taken, hdcp_get_content_protection_from_status() + * reports DRM_MODE_CONTENT_PROTECTION_DESIRED and + * drm_hdcp_update_content_protection() is called. The connector state is + * pre-set to DESIRED so the value is unchanged (no sysfs event) and the deep + * path completes cleanly. + */ +static void dm_test_event_property_update_desired_when_off(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + + work->aconnector[0] = alloc_update_connector(test, connector_status_connected, + conn_state, &adev->ddev); + work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, conn_state->content_protection, + (unsigned int)DRM_MODE_CONTENT_PROTECTION_DESIRED); + KUNIT_EXPECT_EQ(test, work->encryption_status[0], + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF); +} + +/** + * dm_test_event_property_update_enabled_when_encrypted - encrypted maps to ENABLED + * @test: KUnit test context + * + * A fully connected display with TYPE0 content and HDCP1 encryption drives + * the connected path where hdcp_get_content_protection_from_status() reports + * DRM_MODE_CONTENT_PROTECTION_ENABLED. The connector state is pre-set to + * ENABLED so drm_hdcp_update_content_protection() leaves it unchanged. + */ +static void dm_test_event_property_update_enabled_when_encrypted(struct kunit *test) +{ + struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct hdcp_workqueue *work = alloc_test_workqueue_for_property_update(test); + struct drm_connector_state *conn_state; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + conn_state->hdcp_content_type = DRM_MODE_HDCP_CONTENT_TYPE0; + conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + + work->aconnector[0] = alloc_update_connector(test, connector_status_connected, + conn_state, &adev->ddev); + work->encryption_status[0] = MOD_HDCP_ENCRYPTION_STATUS_HDCP1_ON; + + event_property_update(&work->property_update_work); + + KUNIT_EXPECT_EQ(test, conn_state->content_protection, + (unsigned int)DRM_MODE_CONTENT_PROTECTION_ENABLED); + KUNIT_EXPECT_EQ(test, work->encryption_status[0], + MOD_HDCP_ENCRYPTION_STATUS_HDCP1_ON); +} + /* End of tests for event_property_update() */ /* Tests for event_callback() */ @@ -2367,6 +2534,11 @@ static struct kunit_case dm_hdcp_test_cases[] = { KUNIT_CASE(dm_test_process_output_watchdog_stop_and_needed_requeues), /* event_property_update() */ KUNIT_CASE(dm_test_event_property_update_skips_null_connector), + KUNIT_CASE(dm_test_event_property_update_skips_disconnected), + KUNIT_CASE(dm_test_event_property_update_skips_null_state), + KUNIT_CASE(dm_test_event_property_update_skips_null_dev), + KUNIT_CASE(dm_test_event_property_update_desired_when_off), + KUNIT_CASE(dm_test_event_property_update_enabled_when_encrypted), /* event_callback() */ KUNIT_CASE(dm_test_event_callback_cancels_callback_dwork), KUNIT_CASE(dm_test_event_callback_schedules_property_validate), -- cgit v1.2.3 From 1c5e6cd8291f3e3c9f27b592a11bdb8408b2c64f Mon Sep 17 00:00:00 2001 From: Clay King Date: Wed, 15 Jul 2026 17:41:55 -0400 Subject: drm/amd/display: adjust floating point format for gamut remap when needed [Why] The MPCCs different gamut remap matrices allow for S2D13 and S3D12 floating point formats, but only S2D13 is used. There may be cases where more integer bits are required. [How] Switch to S3D12 if any entries in the remap matrix cannot fit in S2D13. Otherwise, prefer the extra precision of S2D13. Communicate the max value that hw can support to dm via dc color caps. Reviewed-by: Dillon Varone Signed-off-by: Clay King Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/basics/conversion.c | 61 ++++++++++++++----- drivers/gpu/drm/amd/display/dc/basics/conversion.h | 12 +++- drivers/gpu/drm/amd/display/dc/dc.h | 1 + drivers/gpu/drm/amd/display/dc/dce/dce_transform.c | 4 +- .../drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c | 8 +-- .../drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c | 8 +-- .../drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c | 8 +-- .../drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c | 3 +- .../gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c | 44 +++++++++----- .../gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c | 69 +++++++++++++++------- .../gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h | 6 +- .../amd/display/dc/resource/dcn30/dcn30_resource.c | 2 + .../display/dc/resource/dcn301/dcn301_resource.c | 2 + .../display/dc/resource/dcn302/dcn302_resource.c | 2 + .../display/dc/resource/dcn303/dcn303_resource.c | 2 + .../amd/display/dc/resource/dcn31/dcn31_resource.c | 2 + .../display/dc/resource/dcn314/dcn314_resource.c | 2 + .../display/dc/resource/dcn315/dcn315_resource.c | 2 + .../display/dc/resource/dcn316/dcn316_resource.c | 2 + .../amd/display/dc/resource/dcn32/dcn32_resource.c | 2 + .../display/dc/resource/dcn321/dcn321_resource.c | 2 + .../amd/display/dc/resource/dcn35/dcn35_resource.c | 2 + .../display/dc/resource/dcn351/dcn351_resource.c | 2 + .../amd/display/dc/resource/dcn36/dcn36_resource.c | 2 + .../display/dc/resource/dcn401/dcn401_resource.c | 2 + .../amd/display/dc/resource/dcn42/dcn42_resource.c | 2 + .../display/dc/resource/dcn42b/dcn42b_resource.c | 2 + 27 files changed, 186 insertions(+), 70 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/basics/conversion.c b/drivers/gpu/drm/amd/display/dc/basics/conversion.c index a1f2350388e2..1c8d4907245a 100644 --- a/drivers/gpu/drm/amd/display/dc/basics/conversion.c +++ b/drivers/gpu/drm/amd/display/dc/basics/conversion.c @@ -26,11 +26,7 @@ #include "dm_services.h" #include "basics/conversion.h" -#define DIVIDER 10000 -/* S2D13 value in [-3.00...0.9999] */ -#define S2D13_MIN (-3 * DIVIDER) -#define S2D13_MAX (3 * DIVIDER) uint16_t fixed_point_to_int_frac( struct fixed31_32 arg, @@ -74,28 +70,44 @@ uint16_t fixed_point_to_int_frac( return result; } /* - * convert_float_matrix - This converts a double into HW register spec defined format S2D13. + * convert_float_matrix - This converts a double into HW register spec defined format S2D13 / S3D12. */ void convert_float_matrix( uint16_t *matrix, - struct fixed31_32 *flt, + const struct fixed31_32 *flt, + enum cm_gamut_coef_format format, uint32_t buffer_size) { - const struct fixed31_32 min_2_13 = - dc_fixpt_from_fraction(S2D13_MIN, DIVIDER); - const struct fixed31_32 max_2_13 = - dc_fixpt_from_fraction(S2D13_MAX, DIVIDER); + struct fixed31_32 min; + struct fixed31_32 max; + uint8_t num_int_bits; + uint8_t num_dec_bits; uint32_t i; + if (format == CM_GAMUT_REMAP_COEF_FORMAT_S2_13) { + min = dc_fixpt_from_fraction(S2D13_MIN, DIVIDER); + max = dc_fixpt_from_fraction(S2D13_MAX, DIVIDER); + num_int_bits = 2; + num_dec_bits = 13; + } else if (format == CM_GAMUT_REMAP_COEF_FORMAT_S3_12) { + min = dc_fixpt_from_fraction(S3D12_MIN, DIVIDER); + max = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); + num_int_bits = 3; + num_dec_bits = 12; + } else { + ASSERT(false); + return; + } + for (i = 0; i < buffer_size; ++i) { uint32_t reg_value = fixed_point_to_int_frac( dc_fixpt_clamp( flt[i], - min_2_13, - max_2_13), - 2, - 13); + min, + max), + num_int_bits, + num_dec_bits); matrix[i] = (uint16_t)reg_value; } @@ -129,10 +141,27 @@ static struct fixed31_32 int_frac_to_fixed_point(uint16_t arg, */ void convert_hw_matrix(struct fixed31_32 *matrix, uint16_t *reg, + enum cm_gamut_coef_format format, uint32_t buffer_size) { - for (uint32_t i = 0; i < buffer_size; ++i) - matrix[i] = int_frac_to_fixed_point(reg[i], 2, 13); + uint8_t num_int_bits; + uint8_t num_dec_bits; + uint32_t i; + + if (format == CM_GAMUT_REMAP_COEF_FORMAT_S2_13) { + num_int_bits = 2; + num_dec_bits = 13; + } else if (format == CM_GAMUT_REMAP_COEF_FORMAT_S3_12) { + num_int_bits = 3; + num_dec_bits = 12; + } else { + ASSERT(false); + return; + } + + for (i = 0; i < buffer_size; ++i) + matrix[i] = int_frac_to_fixed_point(reg[i], + num_int_bits, num_dec_bits); } static uint32_t find_gcd(uint32_t a, uint32_t b) diff --git a/drivers/gpu/drm/amd/display/dc/basics/conversion.h b/drivers/gpu/drm/amd/display/dc/basics/conversion.h index a433cef78496..c7bed2539fa8 100644 --- a/drivers/gpu/drm/amd/display/dc/basics/conversion.h +++ b/drivers/gpu/drm/amd/display/dc/basics/conversion.h @@ -28,6 +28,14 @@ #include "include/fixed31_32.h" +#define DIVIDER 10000 + +#define S2D13_MIN (-4 * DIVIDER) +#define S2D13_MAX (39999) + +#define S3D12_MIN (-8 * DIVIDER) +#define S3D12_MAX (79998) + uint16_t fixed_point_to_int_frac( struct fixed31_32 arg, uint8_t integer_bits, @@ -35,7 +43,8 @@ uint16_t fixed_point_to_int_frac( void convert_float_matrix( uint16_t *matrix, - struct fixed31_32 *flt, + const struct fixed31_32 *flt, + enum cm_gamut_coef_format format, uint32_t buffer_size); void reduce_fraction(uint32_t num, uint32_t den, @@ -43,6 +52,7 @@ void reduce_fraction(uint32_t num, uint32_t den, void convert_hw_matrix(struct fixed31_32 *matrix, uint16_t *reg, + enum cm_gamut_coef_format format, uint32_t buffer_size); static inline unsigned int log_2(unsigned int num) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 75c06a1752b5..07ebd98f0d3b 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -315,6 +315,7 @@ struct mpc_color_caps { struct lut3d_caps mcm_3d_lut_caps; struct lut3d_caps rmcm_3d_lut_caps; bool preblend; + struct fixed31_32 max_gamut_remap_coeff; }; /** diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c b/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c index 12c85c3afd6a..864491bfd7f7 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_transform.c @@ -1145,8 +1145,8 @@ static void dce_transform_set_gamut_remap( for (i = 0; i < GAMUT_MATRIX_SIZE; i++) arr_matrix[i] = adjust->temperature_matrix[i]; - convert_float_matrix( - arr_reg_val, arr_matrix, GAMUT_MATRIX_SIZE); + convert_float_matrix(arr_reg_val, arr_matrix, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, GAMUT_MATRIX_SIZE); program_gamut_remap(xfm_dce, arr_reg_val); } diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c index 9788628cf0ad..ff17a9c9eed0 100644 --- a/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c +++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c @@ -171,8 +171,8 @@ void dpp1_cm_set_gamut_remap( for (i = 0; i < 12; i++) arr_matrix[i] = adjust->temperature_matrix[i]; - convert_float_matrix( - arr_reg_val, arr_matrix, 12); + convert_float_matrix(arr_reg_val, arr_matrix, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12); program_gamut_remap(dpp, arr_reg_val, GAMUT_REMAP_COEFF); } @@ -242,8 +242,8 @@ void dpp1_cm_get_gamut_remap(struct dpp *dpp_base, } adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; - convert_hw_matrix(adjust->temperature_matrix, - arr_reg_val, ARRAY_SIZE(arr_reg_val)); + convert_hw_matrix(adjust->temperature_matrix, arr_reg_val, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, ARRAY_SIZE(arr_reg_val)); } static void dpp1_cm_program_color_matrix( diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c index 26f9485f165d..74cc176d9437 100644 --- a/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c +++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn20/dcn20_dpp_cm.c @@ -227,8 +227,8 @@ void dpp2_cm_set_gamut_remap( for (i = 0; i < 12; i++) arr_matrix[i] = adjust->temperature_matrix[i]; - convert_float_matrix( - arr_reg_val, arr_matrix, 12); + convert_float_matrix(arr_reg_val, arr_matrix, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12); program_gamut_remap(dpp, arr_reg_val, DCN2_GAMUT_REMAP_COEF_A); } @@ -285,8 +285,8 @@ void dpp2_cm_get_gamut_remap(struct dpp *dpp_base, } adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; - convert_hw_matrix(adjust->temperature_matrix, - arr_reg_val, ARRAY_SIZE(arr_reg_val)); + convert_hw_matrix(adjust->temperature_matrix, arr_reg_val, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, ARRAY_SIZE(arr_reg_val)); } void dpp2_program_input_csc( diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c index e01d8e9672fc..8fe3474411b7 100644 --- a/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c +++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn30/dcn30_dpp_cm.c @@ -392,8 +392,8 @@ void dpp3_cm_set_gamut_remap( for (i = 0; i < 12; i++) arr_matrix[i] = adjust->temperature_matrix[i]; - convert_float_matrix( - arr_reg_val, arr_matrix, 12); + convert_float_matrix(arr_reg_val, arr_matrix, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12); //current coefficient set in use REG_GET(CM_GAMUT_REMAP_CONTROL, CM_GAMUT_REMAP_MODE_CURRENT, &gamut_mode); @@ -460,6 +460,6 @@ void dpp3_cm_get_gamut_remap(struct dpp *dpp_base, } adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; - convert_hw_matrix(adjust->temperature_matrix, - arr_reg_val, ARRAY_SIZE(arr_reg_val)); + convert_hw_matrix(adjust->temperature_matrix, arr_reg_val, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, ARRAY_SIZE(arr_reg_val)); } diff --git a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c index 37adf0e6a166..5a611dd14581 100644 --- a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c +++ b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_dwb_cm.c @@ -371,7 +371,8 @@ void dwb3_set_gamut_remap( for (i = 0; i < 12; i++) arr_matrix[i] = adjust.temperature_matrix[i]; - convert_float_matrix(arr_reg_val, arr_matrix, 12); + convert_float_matrix(arr_reg_val, arr_matrix, + CM_GAMUT_REMAP_COEF_FORMAT_S2_13, 12); REG_GET(DWB_GAMUT_REMAP_MODE, DWB_GAMUT_REMAP_MODE_CURRENT, ¤t_mode); diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c b/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c index d7a07e29d23a..9cc6fd38da19 100644 --- a/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c +++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn30/dcn30_mpc.c @@ -1067,7 +1067,8 @@ static void program_gamut_remap( struct dcn30_mpc *mpc30, int mpcc_id, const uint16_t *regval, - uint32_t select) + uint32_t select, + enum cm_gamut_coef_format coef_format) { uint16_t selection = 0; struct color_matrices_reg gam_regs; @@ -1117,9 +1118,11 @@ static void program_gamut_remap( &gam_regs); } - //select coefficient set to use + // select coefficient set + format to use REG_SET(MPCC_GAMUT_REMAP_MODE[mpcc_id], 0, MPCC_GAMUT_REMAP_MODE, selection); + REG_SET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0, + MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format); } void mpc3_set_gamut_remap( @@ -1130,18 +1133,26 @@ void mpc3_set_gamut_remap( struct dcn30_mpc *mpc30 = TO_DCN30_MPC(mpc); int i = 0; uint32_t gamut_mode; + uint16_t hw_matrix[12]; + enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; + struct fixed31_32 abs_max_coef = {0}; if (adjust->gamut_adjust_type != GRAPHICS_GAMUT_ADJUST_TYPE_SW) - program_gamut_remap(mpc30, mpcc_id, NULL, GAMUT_REMAP_BYPASS); + program_gamut_remap(mpc30, mpcc_id, NULL, GAMUT_REMAP_BYPASS, coef_format); else { - struct fixed31_32 arr_matrix[12]; - uint16_t arr_reg_val[12]; + // take largest absolute value of coefficient in temperature matrix + // if S2D13 cannot fit value, use S3D12 + for (i = 0; i < 12; i++) { + if (dc_fixpt_le(abs_max_coef, dc_fixpt_abs(adjust->temperature_matrix[i]))) + abs_max_coef = dc_fixpt_abs(adjust->temperature_matrix[i]); + } - for (i = 0; i < 12; i++) - arr_matrix[i] = adjust->temperature_matrix[i]; + if (dc_fixpt_le(abs_max_coef, dc_fixpt_from_fraction(S2D13_MAX, DIVIDER))) + coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; + else + coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S3_12; - convert_float_matrix( - arr_reg_val, arr_matrix, 12); + convert_float_matrix(hw_matrix, adjust->temperature_matrix, coef_format, 12); //current coefficient set in use REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], MPCC_GAMUT_REMAP_MODE_CURRENT, &gamut_mode); @@ -1153,19 +1164,21 @@ void mpc3_set_gamut_remap( else gamut_mode = 1; - program_gamut_remap(mpc30, mpcc_id, arr_reg_val, gamut_mode); + program_gamut_remap(mpc30, mpcc_id, hw_matrix, gamut_mode, coef_format); } } static void read_gamut_remap(struct dcn30_mpc *mpc30, int mpcc_id, uint16_t *regval, - uint32_t *select) + uint32_t *select, + enum cm_gamut_coef_format *coef_format) { struct color_matrices_reg gam_regs; - //current coefficient set in use + //current coefficient set in use + format REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], MPCC_GAMUT_REMAP_MODE_CURRENT, select); + REG_GET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format); gam_regs.shifts.csc_c11 = mpc30->mpc_shift->MPCC_GAMUT_REMAP_C11_A; gam_regs.masks.csc_c11 = mpc30->mpc_mask->MPCC_GAMUT_REMAP_C11_A; @@ -1202,8 +1215,9 @@ void mpc3_get_gamut_remap(struct mpc *mpc, struct dcn30_mpc *mpc30 = TO_DCN30_MPC(mpc); uint16_t arr_reg_val[12] = {0}; uint32_t select; + enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; - read_gamut_remap(mpc30, mpcc_id, arr_reg_val, &select); + read_gamut_remap(mpc30, mpcc_id, arr_reg_val, &select, &coef_format); if (select == GAMUT_REMAP_BYPASS) { adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; @@ -1211,8 +1225,8 @@ void mpc3_get_gamut_remap(struct mpc *mpc, } adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; - convert_hw_matrix(adjust->temperature_matrix, - arr_reg_val, ARRAY_SIZE(arr_reg_val)); + convert_hw_matrix(adjust->temperature_matrix, arr_reg_val, + coef_format, ARRAY_SIZE(arr_reg_val)); } bool mpc3_program_3dlut( diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c index ce1ee2062e41..d8a4e604a34e 100644 --- a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c +++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.c @@ -302,7 +302,8 @@ void mpc_program_gamut_remap( unsigned int mpcc_id, const uint16_t *regval, enum mpcc_gamut_remap_id gamut_remap_block_id, - enum mpcc_gamut_remap_mode_select mode_select) + enum mpcc_gamut_remap_mode_select mode_select, + enum cm_gamut_coef_format coef_format) { struct color_matrices_reg gamut_regs; struct dcn401_mpc *mpc401 = TO_DCN401_MPC(mpc); @@ -339,8 +340,11 @@ void mpc_program_gamut_remap( regval, &gamut_regs); - //select coefficient set to use, set A (MODE_1) or set B (MODE_2) - REG_SET(MPCC_GAMUT_REMAP_MODE[mpcc_id], 0, MPCC_GAMUT_REMAP_MODE, mode_select); + //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + format + REG_SET(MPCC_GAMUT_REMAP_MODE[mpcc_id], 0, + MPCC_GAMUT_REMAP_MODE, mode_select); + REG_SET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0, + MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format); break; case MPCC_MCM_FIRST_GAMUT_REMAP: @@ -373,9 +377,11 @@ void mpc_program_gamut_remap( regval, &gamut_regs); - //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + format REG_SET(MPCC_MCM_FIRST_GAMUT_REMAP_MODE[mpcc_id], 0, MPCC_MCM_FIRST_GAMUT_REMAP_MODE, mode_select); + REG_SET(MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0, + MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT, coef_format); break; case MPCC_MCM_SECOND_GAMUT_REMAP: @@ -408,9 +414,11 @@ void mpc_program_gamut_remap( regval, &gamut_regs); - //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + //select coefficient set to use, set A (MODE_1) or set B (MODE_2) + format REG_SET(MPCC_MCM_SECOND_GAMUT_REMAP_MODE[mpcc_id], 0, MPCC_MCM_SECOND_GAMUT_REMAP_MODE, mode_select); + REG_SET(MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT[mpcc_id], 0, + MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT, coef_format); break; default: @@ -426,19 +434,28 @@ void mpc401_set_gamut_remap( struct dcn401_mpc *mpc401 = TO_DCN401_MPC(mpc); unsigned int i = 0; uint32_t mode_select = 0; + uint16_t hw_matrix[12]; + enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; + struct fixed31_32 abs_max_coef = {0}; if (adjust->gamut_adjust_type != GRAPHICS_GAMUT_ADJUST_TYPE_SW) { /* Bypass / Disable if type is bypass or hw */ - mpc_program_gamut_remap(mpc, mpcc_id, NULL, - adjust->mpcc_gamut_remap_block_id, MPCC_GAMUT_REMAP_MODE_SELECT_0); + mpc_program_gamut_remap(mpc, mpcc_id, NULL, adjust->mpcc_gamut_remap_block_id, + MPCC_GAMUT_REMAP_MODE_SELECT_0, coef_format); } else { - struct fixed31_32 arr_matrix[12]; - uint16_t arr_reg_val[12]; + // take largest absolute value of coefficient in temperature matrix + // if S2D13 cannot fit value, use S3D12 + for (i = 0; i < 12; i++) { + if (dc_fixpt_le(abs_max_coef, dc_fixpt_abs(adjust->temperature_matrix[i]))) + abs_max_coef = dc_fixpt_abs(adjust->temperature_matrix[i]); + } - for (i = 0; i < 12; i++) - arr_matrix[i] = adjust->temperature_matrix[i]; + if (dc_fixpt_le(abs_max_coef, dc_fixpt_from_fraction(S2D13_MAX, DIVIDER))) + coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; + else + coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S3_12; - convert_float_matrix(arr_reg_val, arr_matrix, 12); + convert_float_matrix(hw_matrix, adjust->temperature_matrix, coef_format, 12); switch (adjust->mpcc_gamut_remap_block_id) { case MPCC_OGAM_GAMUT_REMAP: @@ -463,8 +480,8 @@ void mpc401_set_gamut_remap( else mode_select = MPCC_GAMUT_REMAP_MODE_SELECT_2; - mpc_program_gamut_remap(mpc, mpcc_id, arr_reg_val, - adjust->mpcc_gamut_remap_block_id, mode_select); + mpc_program_gamut_remap(mpc, mpcc_id, hw_matrix, + adjust->mpcc_gamut_remap_block_id, mode_select, coef_format); } } @@ -472,7 +489,8 @@ void mpc_read_gamut_remap(struct mpc *mpc, int mpcc_id, uint16_t *regval, enum mpcc_gamut_remap_id gamut_remap_block_id, - uint32_t *mode_select) + uint32_t *mode_select, + enum cm_gamut_coef_format *coef_format) { struct color_matrices_reg gamut_regs = {0}; struct dcn401_mpc *mpc401 = TO_DCN401_MPC(mpc); @@ -480,7 +498,10 @@ void mpc_read_gamut_remap(struct mpc *mpc, switch (gamut_remap_block_id) { case MPCC_OGAM_GAMUT_REMAP: //current coefficient set in use - REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], MPCC_GAMUT_REMAP_MODE_CURRENT, mode_select); + REG_GET(MPCC_GAMUT_REMAP_MODE[mpcc_id], + MPCC_GAMUT_REMAP_MODE_CURRENT, mode_select); + REG_GET(MPCC_GAMUT_REMAP_COEF_FORMAT[mpcc_id], + MPCC_GAMUT_REMAP_COEF_FORMAT, coef_format); gamut_regs.shifts.csc_c11 = mpc401->mpc_shift->MPCC_GAMUT_REMAP_C11_A; gamut_regs.masks.csc_c11 = mpc401->mpc_mask->MPCC_GAMUT_REMAP_C11_A; @@ -503,7 +524,9 @@ void mpc_read_gamut_remap(struct mpc *mpc, case MPCC_MCM_FIRST_GAMUT_REMAP: REG_GET(MPCC_MCM_FIRST_GAMUT_REMAP_MODE[mpcc_id], - MPCC_MCM_FIRST_GAMUT_REMAP_MODE_CURRENT, mode_select); + MPCC_MCM_FIRST_GAMUT_REMAP_MODE_CURRENT, mode_select); + REG_GET(MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT[mpcc_id], + MPCC_MCM_FIRST_GAMUT_REMAP_COEF_FORMAT, coef_format); gamut_regs.shifts.csc_c11 = mpc401->mpc_shift->MPCC_MCM_FIRST_GAMUT_REMAP_C11_A; gamut_regs.masks.csc_c11 = mpc401->mpc_mask->MPCC_MCM_FIRST_GAMUT_REMAP_C11_A; @@ -526,7 +549,9 @@ void mpc_read_gamut_remap(struct mpc *mpc, case MPCC_MCM_SECOND_GAMUT_REMAP: REG_GET(MPCC_MCM_SECOND_GAMUT_REMAP_MODE[mpcc_id], - MPCC_MCM_SECOND_GAMUT_REMAP_MODE_CURRENT, mode_select); + MPCC_MCM_SECOND_GAMUT_REMAP_MODE_CURRENT, mode_select); + REG_GET(MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT[mpcc_id], + MPCC_MCM_SECOND_GAMUT_REMAP_COEF_FORMAT, coef_format); gamut_regs.shifts.csc_c11 = mpc401->mpc_shift->MPCC_MCM_SECOND_GAMUT_REMAP_C11_A; gamut_regs.masks.csc_c11 = mpc401->mpc_mask->MPCC_MCM_SECOND_GAMUT_REMAP_C11_A; @@ -565,8 +590,10 @@ void mpc401_get_gamut_remap(struct mpc *mpc, { uint16_t arr_reg_val[12] = {0}; uint32_t mode_select = MPCC_GAMUT_REMAP_MODE_SELECT_0; + enum cm_gamut_coef_format coef_format = CM_GAMUT_REMAP_COEF_FORMAT_S2_13; - mpc_read_gamut_remap(mpc, mpcc_id, arr_reg_val, adjust->mpcc_gamut_remap_block_id, &mode_select); + mpc_read_gamut_remap(mpc, mpcc_id, arr_reg_val, + adjust->mpcc_gamut_remap_block_id, &mode_select, &coef_format); if (mode_select == MPCC_GAMUT_REMAP_MODE_SELECT_0) { adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; @@ -574,8 +601,8 @@ void mpc401_get_gamut_remap(struct mpc *mpc, } adjust->gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; - convert_hw_matrix(adjust->temperature_matrix, - arr_reg_val, ARRAY_SIZE(arr_reg_val)); + convert_hw_matrix(adjust->temperature_matrix, arr_reg_val, + coef_format, ARRAY_SIZE(arr_reg_val)); } static const struct mpc_funcs dcn401_mpc_funcs = { diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h index 6d842d7b95c7..3d00d9497ed2 100644 --- a/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h +++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn401/dcn401_mpc.h @@ -242,13 +242,15 @@ void mpc_program_gamut_remap( unsigned int mpcc_id, const uint16_t *regval, enum mpcc_gamut_remap_id gamut_remap_block_id, - enum mpcc_gamut_remap_mode_select mode_select); + enum mpcc_gamut_remap_mode_select mode_select, + enum cm_gamut_coef_format coef_format); void mpc_read_gamut_remap(struct mpc *mpc, int mpcc_id, uint16_t *regval, enum mpcc_gamut_remap_id gamut_remap_block_id, - uint32_t *mode_select); + uint32_t *mode_select, + enum cm_gamut_coef_format *coef_format); void mpc401_get_3dlut_fast_load_status( struct mpc *mpc, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c index d11ab57afcdd..40fc66e64c1b 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn20/dcn20_resource.h" #include "dcn30_resource.h" @@ -2515,6 +2516,7 @@ static bool dcn30_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.dp_hdmi21_pcon_support = true; dc->caps.max_v_total = (1 << 15) - 1; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c index 4ecf448d15b6..551565ae9ef2 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn30/dcn30_resource.h" #include "dcn301_resource.h" @@ -1516,6 +1517,7 @@ static bool dcn301_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.dp_hdmi21_pcon_support = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c index ae8918a4ad3e..2574e706631d 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c @@ -27,6 +27,7 @@ #include "dcn302_resource.h" #include "dcn302/dcn302_dccg.h" #include "irq/dcn302/irq_service_dcn302.h" +#include "basics/conversion.h" #include "dcn30/dcn30_dio_link_encoder.h" #include "dcn30/dcn30_dio_stream_encoder.h" @@ -1416,6 +1417,7 @@ static bool dcn302_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.dp_hdmi21_pcon_support = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c index 75e6f4e46f60..8b24628e3e14 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c @@ -27,6 +27,7 @@ #include "dcn303_resource.h" #include "dcn303/dcn303_dccg.h" #include "irq/dcn303/irq_service_dcn303.h" +#include "basics/conversion.h" #include "dcn30/dcn30_dio_link_encoder.h" #include "dcn30/dcn30_dio_stream_encoder.h" @@ -1360,6 +1361,7 @@ static bool dcn303_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.dp_hdmi21_pcon_support = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c index e29efa452c87..8bb2b8b29cbf 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn31_resource.h" #include "dcn20/dcn20_resource.h" @@ -2123,6 +2124,7 @@ static bool dcn31_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 2; dc->caps.num_of_dpias_per_host_router = 2; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c index f50b3250dcba..372e05487847 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c @@ -33,6 +33,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn314_resource.h" #include "dcn20/dcn20_resource.h" @@ -2051,6 +2052,7 @@ static bool dcn314_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.max_disp_clock_khz_at_vmin = 650000; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c index 8297f2f04c16..6e112ef0a49a 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn315_resource.h" #include "dcn20/dcn20_resource.h" @@ -2095,6 +2096,7 @@ static bool dcn315_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->config.no_native422_support = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c index 046566ad1afe..b9d2567a4180 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn316_resource.h" #include "dcn20/dcn20_resource.h" @@ -1969,6 +1970,7 @@ static bool dcn316_resource_construct( dc->caps.color.mpc.ogam_rom_caps.pq = 0; dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); /* read VBIOS LTTPR caps */ { diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c index 004c5690f876..cd9d2087d14e 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c @@ -32,6 +32,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn32_resource.h" #include "dcn20/dcn20_resource.h" @@ -2448,6 +2449,7 @@ static bool dcn32_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); /* Use pipe context based otg sync logic */ dc->config.use_pipe_ctx_sync_logic = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c index 53fd32249310..ff9c1379896e 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c @@ -31,6 +31,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn32/dcn32_resource.h" #include "dcn321_resource.h" @@ -1939,6 +1940,7 @@ static bool dcn321_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); /* HACK: Force FRL support until BIOS is ready. */ dc->config.force_hdmi21_frl_enc_enable = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c index 5541b89b1350..1b9b6eb8b322 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c @@ -32,6 +32,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn35_resource.h" #include "dml2_0/dml2_wrapper.h" @@ -2078,6 +2079,7 @@ static bool dcn35_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 2; dc->caps.num_of_dpias_per_host_router = 2; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c index 053b4380f57e..2268ced16969 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c @@ -10,6 +10,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn351_resource.h" #include "dcn20/dcn20_resource.h" @@ -2051,6 +2052,7 @@ static bool dcn351_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 2; dc->caps.num_of_dpias_per_host_router = 2; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c index 592000cf9250..b1323e76801e 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c @@ -10,6 +10,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn36_resource.h" #include "dml2_0/dml2_wrapper.h" @@ -2048,6 +2049,7 @@ static bool dcn36_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 2; dc->caps.num_of_dpias_per_host_router = 2; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c index f7ba0509e6bd..181cb7135dfb 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c @@ -10,6 +10,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn401_resource.h" #include "dcn20/dcn20_resource.h" @@ -2131,6 +2132,7 @@ static bool dcn401_resource_construct( dc->caps.color.mpc.ogam_rom_caps.hlg = 0; dc->caps.color.mpc.ocsc = 1; dc->caps.color.mpc.preblend = true; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); /* HACK: Force FRL support until BIOS is ready. */ dc->config.force_hdmi21_frl_enc_enable = true; dc->config.use_spl = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c index 67673a9e5155..dba1b69137f9 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c @@ -9,6 +9,7 @@ #include "dcn42/dcn42_init.h" #include "resource.h" +#include "basics/conversion.h" #include "include/irq_service_interface.h" #include "dcn42_resource.h" @@ -2110,6 +2111,7 @@ static bool dcn42_resource_construct( dc->caps.color.mpc.rmcm_3d_lut_caps.mem_format_support.float_fp1_5_10 = 1; dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_rgba = 1; dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_bgra = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 3; dc->caps.num_of_dpias_per_host_router = 2; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 2334bc5b75b8..dbd6267340ac 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -13,6 +13,7 @@ #include "resource.h" #include "include/irq_service_interface.h" +#include "basics/conversion.h" #include "dcn42b_resource.h" #include "dcn20/dcn20_resource.h" @@ -2080,6 +2081,7 @@ static bool dcn42b_resource_construct( dc->caps.color.mpc.rmcm_3d_lut_caps.mem_format_support.float_fp1_5_10 = 1; dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_rgba = 1; dc->caps.color.mpc.rmcm_3d_lut_caps.mem_pixel_order_support.order_bgra = 1; + dc->caps.color.mpc.max_gamut_remap_coeff = dc_fixpt_from_fraction(S3D12_MAX, DIVIDER); dc->caps.num_of_host_routers = 0; dc->caps.num_of_dpias_per_host_router = 0; -- cgit v1.2.3 From 7a4dd08c3f921576c6a7524f60e4f0e4601835d2 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Thu, 16 Jul 2026 21:45:09 -0400 Subject: drm/amd/display: Prune per-tile Timing from Apple Studio Display Primary Tile [why] The Apple Studio Display primary tile advertises both the full 5120x2880 mode and the per-tile 2560x2880 timing. With the secondary tile already hidden from userspace, the stray 2560x2880 mode on the primary connector can still be picked by compositors, defeating the single 5K stream goal. [how] Prune the per-tile timing from the primary connector during get_modes: when the sink carries the disable_second_tile quirk and the connector is the primary tile (tile_h_loc == 0 && tile_v_loc == 0), drop any probed mode matching the advertised tile size (tile_h_size x tile_v_size) so userspace only sees the full 5120x2880 mode. Fixes: 49521be4809d ("drm/amd/display: hide Apple Studio Display secondary tile") Reviewed-by: Wayne Lin Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 61e992394333..db211884616c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -2804,6 +2804,47 @@ void amdgpu_set_panel_orientation(struct drm_connector *connector) native_mode->vdisplay); } +/* + * The Apple Studio Display primary tile advertises both the full 5120x2880 + * mode and the per-tile 2560x2880 timing. As the secondary tile is hidden from + * userspace (see amdgpu_dm_hide_secondary_tile_from_userspace()), drop the + * per-tile timing from the primary connector so compositors only pick the full + * 5K mode. + */ +static void amdgpu_dm_prune_primary_tile_modes(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + struct drm_display_mode *mode, *t; + + if (!aconnector->dc_sink) + return; + + if (!aconnector->dc_sink->edid_caps.panel_patch.disable_second_tile) + return; + + if (!connector->has_tile) + return; + + /* Only prune the per-tile timing from the primary tile. */ + if (connector->tile_h_loc || connector->tile_v_loc) + return; + + list_for_each_entry_safe(mode, t, &connector->probed_modes, head) { + if (mode->hdisplay != connector->tile_h_size || + mode->vdisplay != connector->tile_v_size) + continue; + + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] pruning per-tile %dx%d timing from primary Apple Studio Display tile\n", + connector->base.id, connector->name, + mode->hdisplay, mode->vdisplay); + + list_del(&mode->head); + drm_mode_destroy(connector->dev, mode); + aconnector->num_modes--; + } +} + STATIC_IFN_KUNIT void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, const struct drm_edid *drm_edid) { @@ -2816,6 +2857,8 @@ STATIC_IFN_KUNIT void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *co amdgpu_dm_connector->num_modes = drm_edid_connector_add_modes(connector); + amdgpu_dm_prune_primary_tile_modes(connector); + /* sorting the probed modes before calling function * amdgpu_dm_get_native_mode() since EDID can have * more than one preferred mode. The modes that are -- cgit v1.2.3 From 040f7925dea9dd455d0434695639ebfe3d46a1bf Mon Sep 17 00:00:00 2001 From: "Huang, Leon" Date: Wed, 15 Jul 2026 00:07:53 +0800 Subject: drm/amd/display: Add get replay residency function [Why&How] Add dc interface to export link service function for getting Replay residency Reviewed-by: Robin Chen Signed-off-by: Huang, Leon Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c | 7 +++++++ drivers/gpu/drm/amd/display/dc/dc.h | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c b/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c index f25d4b3e11ac..14fc4ef482f1 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_exports.c @@ -534,6 +534,13 @@ bool dc_link_set_pr_general_cmd(struct dc_link *link, return link->dc->link_srv->dp_pr_set_general_cmd(link, general_cmd_data); } +void dc_link_edp_replay_residency(const struct dc_link *link, + unsigned int *residency, bool is_start, enum pr_residency_mode mode) +{ + if (link && link->dc && link->dc->link_srv) + link->dc->link_srv->edp_replay_residency(link, residency, is_start, mode); +} + bool dc_link_get_pr_state(const struct dc_link *link, uint64_t *state) { return link->dc->link_srv->dp_pr_get_state(link, state); diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 07ebd98f0d3b..8eaa219132a6 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -2984,6 +2984,14 @@ bool dc_link_update_pr_state(struct dc_link *link, bool dc_link_set_pr_general_cmd(struct dc_link *link, struct dmub_cmd_pr_general_cmd_data *general_cmd_data); +/* + * Measure Panel Replay residency for the given link. + * mode: PR_RESIDENCY_MODE_PHY, PR_RESIDENCY_MODE_ALPM, or + * PR_RESIDENCY_MODE_ENABLEMENT_PERIOD + */ +void dc_link_edp_replay_residency(const struct dc_link *link, + unsigned int *residency, bool is_start, enum pr_residency_mode mode); + /* * Get Panel Replay state: * -- cgit v1.2.3 From 29c0f7c655f47bcbd575ff75e58480df6ec3c9da Mon Sep 17 00:00:00 2001 From: George Zhang Date: Thu, 16 Jul 2026 17:00:01 -0400 Subject: drm/amd/display: Fix divide-by-zero in calculate_mcache_setting on zero viewport If a plane reaches calculate_mcache_setting with a zero-area viewport, calculate_mcache_setting exits early with num_mcaches == 0 and mvmpg_width/height == 0. This will cause a divide-by-zero panic and can also cause an underflow on num_mcaches. Fix this by changing calculate_mcache_setting to bool and adding guards after each calculate_mcache_row_bytes call. If num_mcaches or mvmpg_width/height is zero, return a false. Callers will propagate the failure as a rejected mode, which prevents the panic. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5302 Reviewed-by: Sun peng (Leo) Li Reviewed-by: Dillon Varone Signed-off-by: George Zhang Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 31 +++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c index fa78016b32fc..110ab26a6a39 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c @@ -2453,7 +2453,7 @@ static void calculate_mcache_row_bytes( DML_ASSERT(*p->num_mcaches > 0); } -static void calculate_mcache_setting( +static bool calculate_mcache_setting( struct dml2_core_internal_scratch *scratch, struct dml2_core_calcs_calculate_mcache_setting_params *p) { @@ -2479,7 +2479,7 @@ static void calculate_mcache_setting( *p->lc_comb_mcache = 0; if (!p->dcc_enable) - return; + return true; l->is_dual_plane = dml_is_420(p->source_format) || p->source_format == dml2_rgbe_alpha; @@ -2516,7 +2516,14 @@ static void calculate_mcache_setting( l->l_p.mvmpg_per_mcache_lb = &l->mvmpg_per_mcache_lb_l; calculate_mcache_row_bytes(scratch, &l->l_p); - DML_ASSERT(*p->num_mcaches_l > 0); + if (*p->num_mcaches_l == 0 || + (p->surf_vert ? l->mvmpg_height_l : l->mvmpg_width_l) == 0) { + DML_LOG_VERBOSE("DML::%s: degenerate luma viewport (num_mcaches_l=%u mvmpg_%s_l=%u) — mode not supported\n", + __func__, *p->num_mcaches_l, + p->surf_vert ? "height" : "width", + p->surf_vert ? l->mvmpg_height_l : l->mvmpg_width_l); + return false; + } if (l->is_dual_plane) { l->c_p.num_chans = p->num_chans; @@ -2552,7 +2559,14 @@ static void calculate_mcache_setting( l->c_p.mvmpg_per_mcache_lb = &l->mvmpg_per_mcache_lb_c; calculate_mcache_row_bytes(scratch, &l->c_p); - DML_ASSERT(*p->num_mcaches_c > 0); + if (*p->num_mcaches_c == 0 || + (p->surf_vert ? l->mvmpg_height_c : l->mvmpg_width_c) == 0) { + DML_LOG_VERBOSE("DML::%s: degenerate chroma viewport (num_mcaches_c=%u mvmpg_%s_c=%u) — mode not supported\n", + __func__, *p->num_mcaches_c, + p->surf_vert ? "height" : "width", + p->surf_vert ? l->mvmpg_height_c : l->mvmpg_width_c); + return false; + } } // Sharing for iMALL access @@ -2662,6 +2676,7 @@ static void calculate_mcache_setting( *p->mcache_shift_granularity_l = l->mvmpg_access_width_l; *p->mcache_shift_granularity_c = l->mvmpg_access_width_c; + return true; } static void calculate_mall_bw_overhead_factor( @@ -9490,7 +9505,10 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out calculate_mcache_setting_params->mall_comb_mcache_c = &mode_lib->ms.mall_comb_mcache_c[k]; calculate_mcache_setting_params->lc_comb_mcache = &mode_lib->ms.lc_comb_mcache[k]; - calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params); + if (!calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params)) { + mode_lib->ms.support.ModeSupport = false; + return false; + } } calculate_mall_bw_overhead_factor( @@ -10969,7 +10987,8 @@ static bool dml_core_mode_programming(struct dml2_core_calcs_mode_programming_ex calculate_mcache_setting_params->mall_comb_mcache_l = &mode_lib->mp.mall_comb_mcache_l[k]; calculate_mcache_setting_params->mall_comb_mcache_c = &mode_lib->mp.mall_comb_mcache_c[k]; calculate_mcache_setting_params->lc_comb_mcache = &mode_lib->mp.lc_comb_mcache[k]; - calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params); + if (!calculate_mcache_setting(&mode_lib->scratch, calculate_mcache_setting_params)) + return false; } calculate_mall_bw_overhead_factor( -- cgit v1.2.3 From 5adb54abe5a8e82cbff7f8806db30a5f4924329f Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 16 Feb 2026 09:32:53 -0500 Subject: drm/amd/display: check if dml21_add_phantom_plane() is successful Verify that the phantom plane was allocated to avoid a later segfault. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4970 Fixes: 70839da63605 ("drm/amd/display: Add new DCN401 sources") Reviewed-by: Dillon Varone Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c index 16e1315da2fa..012dff4cf3fa 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_utils.c @@ -359,14 +359,13 @@ void dml21_handle_phantom_streams_planes(const struct dc *dc, struct dc_state *c main_plane = main_stream_status->plane_states[dc_plane_index]; /* create phantom planes for subvp enabled plane */ - dml21_add_phantom_plane(dml_ctx, - dc, - context, - phantom_stream, - main_plane, - &dml_ctx->v21.mode_programming.programming->plane_programming[dml_plane_index]); - - phantoms_added = true; + if (dml21_add_phantom_plane(dml_ctx, + dc, + context, + phantom_stream, + main_plane, + &dml_ctx->v21.mode_programming.programming->plane_programming[dml_plane_index])) + phantoms_added = true; } } } -- cgit v1.2.3 From 6a8b6a7288de6eaf81dad35d4e7d25e5d2ef9523 Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Thu, 18 Jun 2026 19:49:52 -0400 Subject: drm/amd/display: change dcc_rate from 1 to 2 for log use only [why] The dcc_rate value does not affect any watermark, TTU or DLG output; it only affects the Z8 stutter-related logging. The hardware DisplayModeSupported formula uses a dcc_rate of 4, while DML2 currently uses 1. [how] Change dcc_rate from 1 to 2 so the logged value is closer to the hardware formula. Reviewed-by: Dillon Varone Signed-off-by: Charlene Liu Signed-off-by: Matthew Stewart Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c index a76dcde1efbf..47164fa150d9 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_translation_helper.c @@ -447,8 +447,8 @@ static void populate_dml21_dummy_surface_cfg(struct dml2_surface_cfg *surface, c surface->plane0.pitch = ((surface->plane0.width + 127) / 128) * 128; surface->plane1.pitch = 0; surface->dcc.enable = false; - surface->dcc.informative.dcc_rate_plane0 = 1.0; - surface->dcc.informative.dcc_rate_plane1 = 1.0; + surface->dcc.informative.dcc_rate_plane0 = 2.0; + surface->dcc.informative.dcc_rate_plane1 = 2.0; surface->dcc.informative.fraction_of_zero_size_request_plane0 = 0; surface->dcc.informative.fraction_of_zero_size_request_plane1 = 0; surface->tiling = dml2_sw_64kb_2d; @@ -517,8 +517,8 @@ static void populate_dml21_surface_config_from_plane_state( surface->plane1.height = plane_state->plane_size.chroma_size.height; surface->plane1.width = plane_state->plane_size.chroma_size.width; surface->dcc.enable = plane_state->dcc.enable; - surface->dcc.informative.dcc_rate_plane0 = 1.0; - surface->dcc.informative.dcc_rate_plane1 = 1.0; + surface->dcc.informative.dcc_rate_plane0 = 2.0; + surface->dcc.informative.dcc_rate_plane1 = 2.0; surface->dcc.informative.fraction_of_zero_size_request_plane0 = plane_state->dcc.independent_64b_blks; surface->dcc.informative.fraction_of_zero_size_request_plane1 = plane_state->dcc.independent_64b_blks_c; surface->dcc.plane0.pitch = plane_state->dcc.meta_pitch; -- cgit v1.2.3 From 137b56559511588ecb97d5c17c28980d736ca44c Mon Sep 17 00:00:00 2001 From: Alexander Chechik Date: Mon, 22 Jun 2026 11:36:12 -0400 Subject: drm/amd/display: enforce UCLK pstate support in mode_support [Why] mode_support does not require UCLK pstate today, so later PMO optimization stages can push a plane's VActive latency-hiding margin below zero without rechecking that the config still supports UCLK pstate. This can blank the display on high-bandwidth configs. [How] Plumb the PMO-selected per-plane pstate method into mode_support and fail the config only when UCLK pstate is required (method != na) but not supported. For planes committed to a vactive method, require a non-negative VActive latency-hiding margin, and skip the check when all streams are blanked. No-op the PMO DCN42 pstate test (returning false only on the initial candidate so the optimize/FAMS2 stage-3 setup still runs), since reserved time is guaranteed by the override and the vactive margin is now enforced in core mode_support. Reviewed-by: Dillon Varone Signed-off-by: Alexander Chechik Signed-off-by: Matthew Stewart Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c | 8 ++++++ .../dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 18 ++++++++++++ .../dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c | 32 +++++++--------------- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c index 727b01ca18bf..45f1bf5e14bf 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c @@ -554,6 +554,14 @@ bool core_dcn4_mode_support(struct dml2_core_mode_support_in_out *in_out) l->mode_support_ex_params.min_clk_index = in_out->min_clk_index; l->mode_support_ex_params.out_evaluation_info = &in_out->mode_support_result.cfg_support_info.clean_me_up.support_info; + for (i = 0; i < l->svp_expanded_display_cfg.num_planes; i++) { + if (i < in_out->display_cfg->display_config.num_planes) + core->clean_me_up.mode_lib.ms.uclk_pstate_switch_modes[i] = + in_out->display_cfg->stage3.pstate_switch_modes[i]; + else + core->clean_me_up.mode_lib.ms.uclk_pstate_switch_modes[i] = dml2_pstate_method_na; + } + result = dml2_core_calcs_mode_support_ex(&l->mode_support_ex_params); in_out->mode_support_result.cfg_support_info.is_supported = result; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c index 110ab26a6a39..da9a1c4e2bdc 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c @@ -9637,6 +9637,22 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out DML_LOG_VERBOSE("DML::%s: ROBSupport = %u\n", __func__, mode_lib->ms.support.ROBSupport); #endif + mode_lib->ms.support.global_dram_clock_change_support_required = false; + + if (!display_cfg->overrides.all_streams_blanked) { + for (k = 0; k < mode_lib->ms.num_active_planes; k++) { + if (mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_na) + continue; + + mode_lib->ms.support.global_dram_clock_change_support_required = true; + + if ((mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_vactive || + mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_fw_vactive_drr) && + mode_lib->ms.VActiveLatencyHidingMargin[k] < 0) + mode_lib->ms.support.global_dram_clock_change_supported = false; + } + } + /*Mode Support, Voltage State and SOC Configuration*/ { if (mode_lib->ms.support.ScaleRatioAndTapsSupport @@ -9683,6 +9699,8 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out && mode_lib->ms.support.DCCMetaBufferSizeNotExceeded && !mode_lib->ms.support.ExceededMALLSize && mode_lib->ms.support.g6_temp_read_support + && (mode_lib->ms.support.global_dram_clock_change_supported + || !mode_lib->ms.support.global_dram_clock_change_support_required) && ((!display_cfg->hostvm_enable && !s->ImmediateFlipRequired) || mode_lib->ms.support.ImmediateFlipSupport)) { DML_LOG_VERBOSE("DML::%s: mode is supported\n", __func__); mode_lib->ms.support.ModeSupport = true; diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c index 790aef95caa6..57a6a7d5b740 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn42.c @@ -13,8 +13,6 @@ * configurations, ensuring p-state watermark support in the blank period only. */ -static const double MIN_VACTIVE_MARGIN_PCT = 0.25; // We need more than non-zero margin because DET buffer granularity can alter vactive latency hiding - static const struct dml2_pmo_pstate_strategy dcn42_strategy_list_1_display[] = { // VBlank only { @@ -179,7 +177,7 @@ bool pmo_dcn42_init_for_pstate_support(struct dml2_pmo_init_for_pstate_support_i // Figure out which streams can do vactive, and also build up implicit SVP and FAMS2 meta for (stream_index = 0; stream_index < display_config->display_config.num_streams; stream_index++) { - if (dcn4_get_vactive_pstate_margin(display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) >= (int)(MIN_VACTIVE_MARGIN_PCT * pmo->soc_bb->power_management_parameters.dram_clk_change_blackout_us)) + if (dcn4_get_vactive_pstate_margin(display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) >= 0) dcn42_set_bit_in_bitfield(&s->pmo_dcn4.stream_vactive_capability_mask, stream_index); } @@ -259,27 +257,17 @@ bool pmo_dcn42_fams2_optimize_for_pstate_support(struct dml2_pmo_optimize_for_ps bool pmo_dcn42_test_for_pstate_support(struct dml2_pmo_test_for_pstate_support_in_out *in_out) { - const struct dml2_pmo_scratch *s = &in_out->instance->scratch; - bool p_state_supported = true; - unsigned int stream_index; - - if (s->pmo_dcn4.cur_pstate_candidate < 0) + /* Return false on the initial candidate (cur_pstate_candidate == -1) so the + * optimization phase runs at least one optimize iteration; otherwise the + * FAMS2/stage-3 setup in the optimize callback is skipped. + */ + if (in_out->instance->scratch.pmo_dcn4.cur_pstate_candidate < 0) return false; - for (stream_index = 0; stream_index < in_out->base_display_config->display_config.num_streams; stream_index++) { - if (s->pmo_dcn4.pstate_strategy_candidates[s->pmo_dcn4.cur_pstate_candidate].per_stream_pstate_method[stream_index] == dml2_pstate_method_vactive) { - if (dcn4_get_minimum_reserved_time_us_for_planes(in_out->base_display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) < (int)in_out->instance->soc_bb->power_management_parameters.dram_clk_change_blackout_us || - dcn4_get_vactive_pstate_margin(in_out->base_display_config, s->pmo_dcn4.stream_plane_mask[stream_index]) < (int)(MIN_VACTIVE_MARGIN_PCT * in_out->instance->soc_bb->power_management_parameters.dram_clk_change_blackout_us)) { - p_state_supported = false; - break; - } - } else { - p_state_supported = false; - break; - } - } - - return p_state_supported; + /* No-op: reserved time is guaranteed by the override and vactive p-state + * margin is now enforced in core mode support. + */ + return true; } bool pmo_dcn42_initialize(struct dml2_pmo_initialize_in_out *in_out) -- cgit v1.2.3 From a985e937c215fa24172f387eaa13a05843633330 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 12 May 2026 17:05:06 -0400 Subject: drm/amd/display: add DalForceMaxDisplayClock debug option to DML2 [Why & How] need to apply the debug option check for max displayclk. Reviewed-by: Charlene Liu Reviewed-by: Dillon Varone Signed-off-by: Justin Chen Signed-off-by: Matthew Stewart Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c index 4e3d54c92e32..aba7dd65c539 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/dml21_wrapper_fpu.c @@ -155,6 +155,11 @@ static void dml21_calculate_rq_and_dlg_params(const struct dc *dc, struct dc_sta context->bw_ctx.bw.dcn.clk.max_supported_dppclk_khz = in_ctx->v21.dml_init.soc_bb.clk_table.dppclk.clk_values_khz[0]; } + if (dc->config.forced_clocks || dc->debug.max_disp_clk) { + context->bw_ctx.bw.dcn.clk.bw_dispclk_khz = context->bw_ctx.bw.dcn.clk.max_supported_dispclk_khz; + context->bw_ctx.bw.dcn.clk.bw_dppclk_khz = context->bw_ctx.bw.dcn.clk.max_supported_dppclk_khz; + } + /* get global mall allocation */ if (dc->res_pool->funcs->calculate_mall_ways_from_bytes) { context->bw_ctx.bw.dcn.clk.num_ways = dc->res_pool->funcs->calculate_mall_ways_from_bytes(dc, context->bw_ctx.bw.dcn.mall_subvp_size_bytes); -- cgit v1.2.3 From 695bc1971e98d05033f755abfa63eb7a410e3aea Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Fri, 19 Jun 2026 13:36:47 -0400 Subject: drm/amd/display: Fixes for dcn42b_soc_bb.h [why] Some values were found to be incorrect. Reviewed-by: Dillon Varone Signed-off-by: Matthew Stewart Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h index 60ef56419846..5d844ff4b671 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/inc/bounding_boxes/dcn42b_soc_bb.h @@ -210,7 +210,7 @@ static const struct dml2_soc_bb dml2_socbb_dcn42b = { .fabric_datapath_to_dcn_data_return_bytes = 32, .return_bus_width_bytes = 64, .hostvm_min_page_size_kbytes = 4, - .gpuvm_min_page_size_kbytes = 256, + .gpuvm_min_page_size_kbytes = 4, .gpuvm_max_page_table_levels = 1, .hostvm_max_non_cached_page_table_levels = 2, .phy_downspread_percent = 0.38, @@ -236,7 +236,7 @@ static const struct dml2_ip_capabilities dml2_dcn42b_max_ip_caps = { .config_return_buffer_segment_size_in_kbytes = 64, .meta_fifo_size_in_kentries = 32, .compressed_buffer_segment_size_in_kbytes = 64, - .cursor_buffer_size = 24, + .cursor_buffer_size = 42, .max_flip_time_us = 110, .max_flip_time_lines = 50, .hostvm_mode = 0, -- cgit v1.2.3 From fa0300333db07ca4ad56c9879f155faafd3f7079 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Fri, 19 Jun 2026 13:41:36 -0400 Subject: drm/amd/display: Fix rounding errors in CalculatePrefetchSchedule [why] Rounding errors were causing mode validation to fail in some cases when it should not. (IE. Increasing fclk from a lower value could lead to validation failure, which should not happen.) Reviewed-by: Dillon Varone Signed-off-by: Matthew Stewart Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c index da9a1c4e2bdc..4ca6a4521f11 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c @@ -5622,7 +5622,8 @@ static bool CalculatePrefetchSchedule(struct dml2_core_internal_scratch *scratch + 2 * (p->PixelPTEBytesPerRow * p->HostVMInefficiencyFactor + p->meta_row_bytes + tdlut_row_bytes) + *p->prefetch_sw_bytes) / (*p->Tpre_rounded - *p->Tno_bw); - s->Tsw_est1 = *p->prefetch_sw_bytes / s->prefetch_bw1; + /* due to rounding, VM can clamp to +1/4, Row can clamp to +2/4, giving SW less time */ + s->Tsw_est1 = *p->prefetch_sw_bytes / s->prefetch_bw1 - 3.0 * s->LineTime / 4.0; } else s->prefetch_bw1 = 0; @@ -5646,7 +5647,8 @@ static bool CalculatePrefetchSchedule(struct dml2_core_internal_scratch *scratch if (*p->Tpre_rounded - *p->Tno_bw - 2.0 * s->Tr0_trips_rounded > 0) { s->prefetch_bw2 = (vm_bytes * p->HostVMInefficiencyFactor + *p->prefetch_sw_bytes) / (*p->Tpre_rounded - *p->Tno_bw - 2.0 * s->Tr0_trips_rounded); - s->Tsw_est2 = *p->prefetch_sw_bytes / s->prefetch_bw2; + /* due to rounding, VM can clamp to +1/4 */ + s->Tsw_est2 = *p->prefetch_sw_bytes / s->prefetch_bw2 - 1.0 * s->LineTime / 4.0; } else s->prefetch_bw2 = 0; @@ -5660,7 +5662,8 @@ static bool CalculatePrefetchSchedule(struct dml2_core_internal_scratch *scratch if (*p->Tpre_rounded - s->Tvm_trips_rounded > 0) { s->prefetch_bw3 = (2 * (p->PixelPTEBytesPerRow * p->HostVMInefficiencyFactor + p->meta_row_bytes + tdlut_row_bytes) + *p->prefetch_sw_bytes) / (*p->Tpre_rounded - s->Tvm_trips_rounded); - s->Tsw_est3 = *p->prefetch_sw_bytes / s->prefetch_bw3; + /* due to rounding, Row can clamp to +2/4 */ + s->Tsw_est3 = *p->prefetch_sw_bytes / s->prefetch_bw3 - 2.0 * s->LineTime / 4.0; } else s->prefetch_bw3 = 0; -- cgit v1.2.3 From 9fcd9c754565408e70dbfc116be1b7373e518e04 Mon Sep 17 00:00:00 2001 From: Alexander Chechik Date: Tue, 23 Jun 2026 23:50:56 -0400 Subject: drm/amd/display: plumb PMO per-plane pstate methods into mode_support [Why] mode_support reads mode_lib.ms.uclk_pstate_switch_modes to enforce the vactive pstate margin, but nothing populates it: the memset clears it and the PMO selection is never passed in. The check always sees na and never runs, so a plane with negative vactive margin can still pass and blank the display. [How] Add a const per-plane pstate-method pointer to mode_support_ex, point it at stage3.pstate_switch_modes when stage 3 has run (NULL otherwise), and copy it into mode_lib.ms after the memset. Scope the support-required check to the vactive methods it governs (vactive, fw_vactive_drr) so SVP, DRR and vblank planes are not rejected by the vactive support flag. Reviewed-by: Charlene Liu Signed-off-by: Alexander Chechik Signed-off-by: Matthew Stewart Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c | 10 +++------- .../dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 17 +++++++++++++---- .../dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h | 1 + 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c index 45f1bf5e14bf..6ea36aedcf20 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4.c @@ -554,13 +554,9 @@ bool core_dcn4_mode_support(struct dml2_core_mode_support_in_out *in_out) l->mode_support_ex_params.min_clk_index = in_out->min_clk_index; l->mode_support_ex_params.out_evaluation_info = &in_out->mode_support_result.cfg_support_info.clean_me_up.support_info; - for (i = 0; i < l->svp_expanded_display_cfg.num_planes; i++) { - if (i < in_out->display_cfg->display_config.num_planes) - core->clean_me_up.mode_lib.ms.uclk_pstate_switch_modes[i] = - in_out->display_cfg->stage3.pstate_switch_modes[i]; - else - core->clean_me_up.mode_lib.ms.uclk_pstate_switch_modes[i] = dml2_pstate_method_na; - } + l->mode_support_ex_params.uclk_pstate_switch_modes = + in_out->display_cfg->stage3.performed ? + in_out->display_cfg->stage3.pstate_switch_modes : NULL; result = dml2_core_calcs_mode_support_ex(&l->mode_support_ex_params); diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c index 4ca6a4521f11..09452f3e49db 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c @@ -8057,6 +8057,16 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out memset(&mode_lib->ms, 0, sizeof(struct dml2_core_internal_mode_support)); mode_lib->ms.num_active_planes = display_cfg->num_planes; + + for (k = 0; k < mode_lib->ms.num_active_planes; k++) { + if (in_out_params->uclk_pstate_switch_modes && + !dml_is_phantom_pipe(&display_cfg->plane_descriptors[k])) + mode_lib->ms.uclk_pstate_switch_modes[k] = + in_out_params->uclk_pstate_switch_modes[k]; + else + mode_lib->ms.uclk_pstate_switch_modes[k] = dml2_pstate_method_na; + } + get_stream_output_bpp(s->OutputBpp, display_cfg); mode_lib->ms.state_idx = in_out_params->min_clk_index; @@ -9644,14 +9654,13 @@ static bool dml_core_mode_support(struct dml2_core_calcs_mode_support_ex *in_out if (!display_cfg->overrides.all_streams_blanked) { for (k = 0; k < mode_lib->ms.num_active_planes; k++) { - if (mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_na) + if (mode_lib->ms.uclk_pstate_switch_modes[k] != dml2_pstate_method_vactive && + mode_lib->ms.uclk_pstate_switch_modes[k] != dml2_pstate_method_fw_vactive_drr) continue; mode_lib->ms.support.global_dram_clock_change_support_required = true; - if ((mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_vactive || - mode_lib->ms.uclk_pstate_switch_modes[k] == dml2_pstate_method_fw_vactive_drr) && - mode_lib->ms.VActiveLatencyHidingMargin[k] < 0) + if (mode_lib->ms.VActiveLatencyHidingMargin[k] < 0) mode_lib->ms.support.global_dram_clock_change_supported = false; } } diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h index 131cec64aa48..b8af72392176 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h @@ -2333,6 +2333,7 @@ struct dml2_core_calcs_mode_support_ex { int min_clk_index; //unsigned int in_state_index; struct dml2_core_internal_mode_support_info *out_evaluation_info; + const enum dml2_pstate_method *uclk_pstate_switch_modes; }; struct core_display_cfg_support_info; -- cgit v1.2.3 From 9afc6186faa64f5d00cfccbb0931b2ddb03975e8 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Wed, 15 Jul 2026 15:09:34 -0400 Subject: drm/amd/display: dispatch compressed FRL cap check inside dml1_frl_cap_chk_inter [why] DSC over HDMI FRL (e.g. 4k144) was pruned by DML mode support because the compressed FRL cap check was never reached. dml32_TruncToValidBPP() validates the FRL output by calling dml1_frl_cap_chk_inter() directly. The compressed-vs-uncompressed dispatch (if params->compressed -> dml1_frl_cap_chk_compressed()) had been moved up into the top-level dml1_frl_cap_chk() wrapper, leaving dml1_frl_cap_chk_inter() as uncompressed-only. As a result a DSC stream routed through TruncToValidBPP was validated against the full uncompressed bandwidth, failed the cap check, and the mode was pruned (vlevel == num_states). [how] Move the compressed dispatch back into dml1_frl_cap_chk_inter() so every caller of _inter() (including TruncToValidBPP) honours params->compressed. This matches the internal DAL tree. Reviewed-by: Harry Wentland Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c index 260f58a97fc7..da2c92e5c02f 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c +++ b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c @@ -630,17 +630,17 @@ enum frl_cap_chk_result dml1_frl_cap_chk(struct frl_cap_chk_params *params) { struct frl_cap_chk_intermediates inter; -#if defined (CONFIG_DRM_AMD_DC_FP) - if (params->compressed) - return dml1_frl_cap_chk_compressed(params, &inter); -#endif - return dml1_frl_cap_chk_inter(params, &inter); } enum frl_cap_chk_result dml1_frl_cap_chk_inter(struct frl_cap_chk_params *params, struct frl_cap_chk_intermediates *inter) { +#if defined (CONFIG_DRM_AMD_DC_FP) + if (params->compressed) + return dml1_frl_cap_chk_compressed(params, inter); +#endif + return dml1_frl_cap_chk_uncompressed(params, inter); } -- cgit v1.2.3 From 1e1534778e21f12f2f9e9bc13781d386e97cd03e Mon Sep 17 00:00:00 2001 From: "Santhosh, Ashwin" Date: Sun, 19 Jul 2026 13:13:30 -0400 Subject: drm/amd/display: Promote DC to 3.2.391 This DC patchset brings improvements in multiple areas. In summary, we have: * Expand KUnit test * dm refactor ongoing * Fix apple 5k tiled monitor light up * Fix frl dsc upstream mistake * dcn42 fixes Reviewed-by: Sunpeng Li Signed-off-by: Santhosh, Ashwin Signed-off-by: Fangzhi Zuo Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 8eaa219132a6..63c0dec85697 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -66,7 +66,7 @@ struct dcn_dsc_reg_state; struct dcn_optc_reg_state; struct dcn_dccg_reg_state; -#define DC_VER "3.2.390" +#define DC_VER "3.2.391" /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC -- cgit v1.2.3 From 149f894a2a8bac181af7b979180e7578e2190bb0 Mon Sep 17 00:00:00 2001 From: "Stanley.Yang" Date: Tue, 14 Jul 2026 17:45:50 +0800 Subject: drm/amdgpu/ras: use vram_base_offset for UMC inject address Cover both XGMI hive offset and A+A platform MC FB offset in one calculation. Signed-off-by: Stanley.Yang Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c index c2285fde8b3c..f9ee297a6745 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c @@ -73,15 +73,6 @@ static int amdgpu_ras_trigger_error_end(struct ras_core_context *ras_core, return 0; } -static uint64_t local_addr_to_xgmi_global_addr(struct ras_core_context *ras_core, - uint64_t addr) -{ - struct amdgpu_device *adev = (struct amdgpu_device *)ras_core->dev; - struct amdgpu_xgmi *xgmi = &adev->gmc.xgmi; - - return (addr + xgmi->physical_node_id * xgmi->node_segment_size); -} - /* * UMC error injection is dispatched by the RAS TA using the injection method * carried in struct ras_cmd_inject_error_req. Only the "coherent" methods @@ -154,9 +145,11 @@ static int amdgpu_ras_inject_error(struct ras_core_context *ras_core, return RAS_CMD__ERROR_INVALID_INPUT_DATA; } - /* Calculate XGMI relative offset */ - if (adev->gmc.xgmi.num_physical_nodes > 1) - req->address = local_addr_to_xgmi_global_addr(ras_core, req->address); + /* + * Calculate XGMI relative offset if in XGMI hive, + * adapt to A+A platform with mc fb offset calculated. + */ + req->address += adev->vm_manager.vram_base_offset; } } -- cgit v1.2.3 From cba4928cdffaa0f9012acff0ec4f896320107077 Mon Sep 17 00:00:00 2001 From: chong li Date: Wed, 15 Jul 2026 18:41:15 +0800 Subject: drm/amdgpu: reduce early full GPU access during SR-IOV init Allow early FB reads to fall back to BAR0 when the VRAM aperture is not ready. This lets SR-IOV VFs consume host-provided init data before requesting full GPU access. For ASICs that support request_init_data, defer full GPU access until after non-GPU early init to shorten the full-access window. Legacy ASICs(before NV12) do not send request_init_data; the host dumps init data only during full GPU access, so keep the original early full-access request path for them. Signed-off-by: chong li Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 88 ++++++++++++++++++++++++++++-- drivers/gpu/drm/amd/amdgpu/mxgpu_ai.c | 7 ++- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 472e96ae884e..638dfaf127fd 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -745,6 +745,64 @@ void amdgpu_device_mm_access(struct amdgpu_device *adev, loff_t pos, drm_dev_exit(idx); } +#ifdef CONFIG_64BIT +/* + * During early SR-IOV VF init, host-provided init data can live in FB before + * the normal VRAM aperture mapping is ready. Use a temporary BAR0 mapping for + * reads only, and verify it matches the VRAM aperture when aperture information + * is already available. + */ +static int amdgpu_device_read_fb_via_bar0(struct amdgpu_device *adev, + u64 offset, void *buf, size_t size) +{ + resource_size_t aper_base, aper_size, bar_start, bar_size, map_base; + void __iomem *vram; + size_t map_offset, map_size; + unsigned long flags; + u64 end; + + if (!buf || !size) + return -EINVAL; + + flags = pci_resource_flags(adev->pdev, 0); + if ((flags & IORESOURCE_UNSET) || !(flags & IORESOURCE_MEM)) + return -EINVAL; + + bar_start = pci_resource_start(adev->pdev, 0); + bar_size = pci_resource_len(adev->pdev, 0); + if (!bar_size) + return -ENODEV; + + aper_base = adev->gmc.aper_base; + aper_size = adev->gmc.visible_vram_size ? adev->gmc.visible_vram_size : + adev->gmc.aper_size; + + if (aper_base || aper_size) { + if (aper_base != bar_start || aper_size > bar_size) + return -EINVAL; + } else { + aper_base = bar_start; + aper_size = bar_size; + } + + if (check_add_overflow(offset, size, &end) || end > aper_size) + return -EINVAL; + + map_offset = offset_in_page(offset); + map_base = aper_base + (offset & PAGE_MASK); + map_size = PAGE_ALIGN(map_offset + size); + + vram = ioremap_wc(map_base, map_size); + if (!vram) + return -ENOMEM; + + memcpy_fromio(buf, (u8 __iomem *)vram + map_offset, size); + iounmap(vram); + + return 0; +} +#endif + /** * amdgpu_device_aper_access - access vram by vram aperture * @@ -764,8 +822,12 @@ size_t amdgpu_device_aper_access(struct amdgpu_device *adev, loff_t pos, size_t count = 0; uint64_t last; - if (!adev->mman.aper_base_kaddr) + if (!adev->mman.aper_base_kaddr) { + /* Writes still require the regular aperture/MM path. */ + if (!write && !amdgpu_device_read_fb_via_bar0(adev, pos, buf, size)) + return size; return 0; + } last = min(pos + size, adev->gmc.visible_vram_size); if (last > pos) { @@ -1862,16 +1924,25 @@ static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) { struct amdgpu_ip_block *ip_block; struct pci_dev *parent; - bool total, skip_bios; + bool total, skip_bios, early_full_gpu_access = false; uint32_t bios_flags; int i, r; amdgpu_device_enable_virtual_display(adev); if (amdgpu_sriov_vf(adev)) { - r = amdgpu_virt_request_full_gpu(adev, true); - if (r) - return r; + /* + * Legacy hosts do not provide init data before early init, so + * keep the original early full GPU access request for them. Newer + * hosts publish the init data through VF FB, which lets us defer + * full GPU access until after non-GPU early init work is done. + */ + early_full_gpu_access = (adev->virt.req_init_data_ver == 0); + if (early_full_gpu_access) { + r = amdgpu_virt_request_full_gpu(adev, true); + if (r) + return r; + } r = amdgpu_virt_init_critical_region(adev); if (r) @@ -2034,6 +2105,13 @@ static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) if (!total) return -ENODEV; + /* Request full GPU access only for the remaining SR-IOV init work. */ + if (amdgpu_sriov_vf(adev) && !early_full_gpu_access) { + r = amdgpu_virt_request_full_gpu(adev, true); + if (r) + return r; + } + if (adev->gmc.xgmi.supported) amdgpu_xgmi_early_init(adev); diff --git a/drivers/gpu/drm/amd/amdgpu/mxgpu_ai.c b/drivers/gpu/drm/amd/amdgpu/mxgpu_ai.c index 9a40107a0869..fec08f235b92 100644 --- a/drivers/gpu/drm/amd/amdgpu/mxgpu_ai.c +++ b/drivers/gpu/drm/amd/amdgpu/mxgpu_ai.c @@ -185,8 +185,11 @@ static int xgpu_ai_send_access_requests(struct amdgpu_device *adev, } else if (req == IDH_REQ_GPU_INIT_DATA){ /* Dummy REQ_GPU_INIT_DATA handling */ r = xgpu_ai_poll_msg(adev, IDH_REQ_GPU_INIT_DATA_READY); - /* version set to 0 since dummy */ - adev->virt.req_init_data_ver = 0; + /* + * AI uses the GPU_CRIT_REGION_V1 layout in practice, so fix the + * dummy version value to match the actual init-data format. + */ + adev->virt.req_init_data_ver = GPU_CRIT_REGION_V1; } return 0; -- cgit v1.2.3 From 9ba8e75d2ee850294be33afc6aeba6ecdb95a6c6 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Fri, 24 Jul 2026 12:03:30 +0800 Subject: drm/amd/ras: add device lost check to early exit psp cmd wait loop Add device lost status check in PSP fence wait loop to exit polling early when GPU device lost Signed-off-by: Ce Sun Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c | 3 +++ drivers/gpu/drm/amd/ras/rascore/ras.h | 2 ++ drivers/gpu/drm/amd/ras/rascore/ras_core.c | 14 ++++++++++++++ drivers/gpu/drm/amd/ras/rascore/ras_psp.c | 3 +++ 4 files changed, 22 insertions(+) diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c index e4444798bc73..0b36b689fcfc 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c @@ -171,6 +171,9 @@ static int amdgpu_ras_sys_check_gpu_status(struct ras_core_context *ras_core, if (amdgpu_in_reset(adev) || amdgpu_ras_in_recovery(adev)) gpu_status |= RAS_GPU_STATUS__IN_RESET; + if (amdgpu_device_bus_status_check(adev)) + gpu_status |= RAS_GPU_STATUS__DEVICE_LOST; + if (amdgpu_sriov_vf(adev)) gpu_status |= RAS_GPU_STATUS__IS_VF; diff --git a/drivers/gpu/drm/amd/ras/rascore/ras.h b/drivers/gpu/drm/amd/ras/rascore/ras.h index d0018f87172e..8c3d603ba801 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras.h +++ b/drivers/gpu/drm/amd/ras/rascore/ras.h @@ -137,6 +137,7 @@ enum ras_gpu_status { RAS_GPU_STATUS__IN_RESET = 0x2, RAS_GPU_STATUS__IS_RMA = 0x4, RAS_GPU_STATUS__IS_VF = 0x8, + RAS_GPU_STATUS__DEVICE_LOST = 0x10, }; enum ras_fw_eeprom_cmd { @@ -374,6 +375,7 @@ int ras_core_query_block_ecc_data(struct ras_core_context *ras_core, bool ras_core_gpu_in_reset(struct ras_core_context *ras_core); bool ras_core_gpu_is_rma(struct ras_core_context *ras_core); bool ras_core_gpu_is_vf(struct ras_core_context *ras_core); +bool ras_core_gpu_device_lost(struct ras_core_context *ras_core); bool ras_core_handle_nbio_irq(struct ras_core_context *ras_core, void *data); int ras_core_handle_fatal_error(struct ras_core_context *ras_core); diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_core.c b/drivers/gpu/drm/amd/ras/rascore/ras_core.c index f8a027b60b38..75e8216fae8a 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_core.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_core.c @@ -147,6 +147,20 @@ bool ras_core_gpu_is_vf(struct ras_core_context *ras_core) return (status & RAS_GPU_STATUS__IS_VF) ? true : false; } +bool ras_core_gpu_device_lost(struct ras_core_context *ras_core) +{ + uint32_t status = 0; + + if (!ras_core) + return false; + + if (ras_core->sys_fn && + ras_core->sys_fn->check_gpu_status) + ras_core->sys_fn->check_gpu_status(ras_core, &status); + + return (status & RAS_GPU_STATUS__DEVICE_LOST) ? true : false; +} + bool ras_core_gpu_is_rma(struct ras_core_context *ras_core) { if (!ras_core) diff --git a/drivers/gpu/drm/amd/ras/rascore/ras_psp.c b/drivers/gpu/drm/amd/ras/rascore/ras_psp.c index 358f602b167d..35264c119bbb 100644 --- a/drivers/gpu/drm/amd/ras/rascore/ras_psp.c +++ b/drivers/gpu/drm/amd/ras/rascore/ras_psp.c @@ -294,6 +294,9 @@ static int send_psp_cmd(struct ras_core_context *ras_core, psp_ctx->in_fence_value) { if (--timeout == 0) break; + + if (ras_core_gpu_device_lost(ras_core)) + break; /* * Shouldn't wait for timeout when err_event_athub occurs, * because gpu reset thread triggered and lock resource should -- cgit v1.2.3 From b84d8db29e0cbd289f0bfe6a3f18ab487ddc9b5e Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 23 Jul 2026 14:41:37 +0800 Subject: drm/amdgpu: normalize error return of RAS command wrapper functions User-space read/write syscalls interpret positive driver return values as successful transfer sizes. Our current RAS wrappers return positive error codes, so we must convert them to standard negative error codes. Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 4 ++-- drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 24fd24e8d874..297f8b04c4eb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -1436,7 +1436,7 @@ static int amdgpu_uniras_clear_badpages_info(struct amdgpu_device *adev) &req, sizeof(req), NULL, 0); if (ret) { dev_err(adev->dev, "Failed to clear bad pages info, ret: %d\n", ret); - return ret; + return -EINVAL; } return 0; @@ -1536,7 +1536,7 @@ static int amdgpu_uniras_error_inject(struct amdgpu_device *adev, inject_req.method = info->value; return amdgpu_ras_mgr_handle_ras_cmd(adev, RAS_CMD__INJECT_ERROR, - &inject_req, sizeof(inject_req), &rsp, sizeof(rsp)); + &inject_req, sizeof(inject_req), &rsp, sizeof(rsp)) ? -EINVAL : 0; } /* wrapper of psp_ras_trigger_error */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c index 0d34f0eca991..686c92e96025 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -526,7 +526,7 @@ static ssize_t amdgpu_ras_cper_debugfs_read(struct file *f, char __user *buf, snapshot_req, sizeof(struct ras_cmd_cper_snapshot_req), snapshot_rsp, sizeof(struct ras_cmd_cper_snapshot_rsp)); if (r) - return r; + return -EINVAL; if (!snapshot_rsp->total_cper_num) { if (!read_header) @@ -568,7 +568,7 @@ static ssize_t amdgpu_ras_cper_debugfs_read(struct file *f, char __user *buf, record_rsp, sizeof(struct ras_cmd_cper_record_rsp)); if (r) - return r; + return -EINVAL; if (!record_rsp->real_data_size || !record_rsp->real_cper_num) break; -- cgit v1.2.3 From 218c0b4c5b5f6aa0d6d4745adde3edbdfb6486fc Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 23 Jul 2026 16:06:07 +0800 Subject: drm/amdgpu: avoid resource leak on SR-IOV VF with AMDGIM_FEATURE_RAS_CPER enabled Original amdgpu_cper_fini skips all CPER cleanup work for any SR-IOV VF directly. When AMDGIM_FEATURE_RAS_CPER is enabled on VF side, CPER related buffers/workitems are allocated during initialization, but the old code returns early without releasing these resources, which leads to kernel memory leak. Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c index 0af8b7be326e..373e47050630 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c @@ -519,7 +519,7 @@ int amdgpu_cper_deferred_init(struct amdgpu_device *adev) int amdgpu_cper_fini(struct amdgpu_device *adev) { - if (amdgpu_sriov_vf(adev)) + if (amdgpu_sriov_vf(adev) && !amdgpu_sriov_ras_cper_en(adev)) return 0; adev->cper.enabled = false; -- cgit v1.2.3 From ff57e223ab105795b05d3ef3f3c35a5a441bcbaa Mon Sep 17 00:00:00 2001 From: William Palacek Date: Wed, 22 Jul 2026 11:20:56 -0400 Subject: drm/amdkfd: hold event_mutex while checkpointing CRIU events kfd_criu_checkpoint_events() counts the entries in p->event_idr via kfd_get_num_events(), allocates an array sized to that count, and then walks the same IDR to fill it. Neither the count nor the walk holds p->event_mutex. The CRIU checkpoint caller holds only p->mutex. Event create and destroy (kfd_event_create()/kfd_event_destroy()) take p->event_mutex and do not take p->mutex, so a second thread in the same process can insert or remove events between the count and the walk. If an event is inserted, the walk iterates more entries than were counted and writes past the end of the ev_privs allocation; if an event is removed, the walk dereferences an entry that is being freed. Hold p->event_mutex across the count and the walk so both observe a consistent view of p->event_idr. The lock is released before copy_to_user(), which only touches the local buffer. The caller already holds p->mutex and the create/destroy paths never take p->mutex, so the p->mutex -> p->event_mutex order is not inverted and no deadlock is introduced. Fixes: 40e8a766a761 ("drm/amdkfd: CRIU checkpoint and restore events") Signed-off-by: William Palacek Reviewed-by: Alysa Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_events.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_events.c index 9e0f57cfa3bf..5fa156e89a73 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.c @@ -476,15 +476,27 @@ int kfd_criu_checkpoint_events(struct kfd_process *p, int ret = 0; struct kfd_event *ev; uint32_t ev_id; + uint32_t num_events; - uint32_t num_events = kfd_get_num_events(p); + /* Serialize the count and the walk below against concurrent event + * create/destroy. Those paths take only p->event_mutex, not the + * p->mutex held by the CRIU checkpoint caller, so without this the + * event_idr can grow between kfd_get_num_events() and the loop and the + * walk writes past the ev_privs allocation. + */ + mutex_lock(&p->event_mutex); - if (!num_events) + num_events = kfd_get_num_events(p); + if (!num_events) { + mutex_unlock(&p->event_mutex); return 0; + } ev_privs = kvzalloc(num_events * sizeof(*ev_privs), GFP_KERNEL); - if (!ev_privs) + if (!ev_privs) { + mutex_unlock(&p->event_mutex); return -ENOMEM; + } idr_for_each_entry(&p->event_idr, ev, ev_id) { @@ -525,6 +537,8 @@ int kfd_criu_checkpoint_events(struct kfd_process *p, i++; } + mutex_unlock(&p->event_mutex); + ret = copy_to_user(user_priv_data + *priv_data_offset, ev_privs, num_events * sizeof(*ev_privs)); if (ret) { -- cgit v1.2.3 From bc1e9d3964381c27f815f2cbe9806a145ee211c8 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:16:20 -0400 Subject: drm/amdgpu/imu12: WARN() rather than BUG() There's no need to crash the kernel for this case. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/imu_v12_0.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/imu_v12_0.c b/drivers/gpu/drm/amd/amdgpu/imu_v12_0.c index 58cd87db8061..4256225f2495 100644 --- a/drivers/gpu/drm/amd/amdgpu/imu_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/imu_v12_0.c @@ -382,8 +382,9 @@ static void imu_v12_0_program_rlc_ram(struct amdgpu_device *adev) (const u32)ARRAY_SIZE(imu_rlc_ram_golden_12_0_1)); break; default: - BUG(); - break; + WARN(1, "Invalid GFX/IMU IP version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return; } //Indicate the latest entry -- cgit v1.2.3 From f3b562367850ac897dcf2eba6e76894b5da81007 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:24:02 -0400 Subject: drm/amdgpu/mes11: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index b72da75dd851..553f9d39add8 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -86,7 +86,7 @@ static void mes_v11_0_ring_set_wptr(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -97,12 +97,15 @@ static u64 mes_v11_0_ring_get_rptr(struct amdgpu_ring *ring) static u64 mes_v11_0_ring_get_wptr(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -294,8 +297,8 @@ static int convert_to_mes_queue_type(int queue_type) else if (queue_type == AMDGPU_RING_TYPE_SDMA) return MES_QUEUE_TYPE_SDMA; else - BUG(); - return -1; + WARN(1, "Invalid queue type %d\n", queue_type); + return MES_QUEUE_TYPE_GFX; } static int convert_to_mes_priority_level(int priority_level) @@ -1599,12 +1602,14 @@ static int mes_v11_0_queue_init(struct amdgpu_device *adev, struct amdgpu_ring *ring; int r; - if (pipe == AMDGPU_MES_KIQ_PIPE) + if (pipe == AMDGPU_MES_KIQ_PIPE) { ring = &adev->gfx.kiq[0].ring; - else if (pipe == AMDGPU_MES_SCHED_PIPE) + } else if (pipe == AMDGPU_MES_SCHED_PIPE) { ring = &adev->mes.ring[0]; - else - BUG(); + } else { + WARN(1, "Invalid MES pipe %d\n", pipe); + return -EINVAL; + } if ((pipe == AMDGPU_MES_SCHED_PIPE) && (amdgpu_in_reset(adev) || adev->in_suspend)) { @@ -1687,7 +1692,7 @@ static int mes_v11_0_mqd_sw_init(struct amdgpu_device *adev, else if (pipe == AMDGPU_MES_SCHED_PIPE) ring = &adev->mes.ring[0]; else - BUG(); + return -EINVAL; if (ring->mqd_obj) return 0; -- cgit v1.2.3 From cab54b4e2dbbdfe3a00f70e3e5e2b5dac3b030d9 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:27:08 -0400 Subject: drm/amdgpu/mes12: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v12_0.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c index 04465804c254..3b2b955cf82e 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_0.c @@ -59,7 +59,7 @@ static void mes_v12_0_ring_set_wptr(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -70,12 +70,15 @@ static u64 mes_v12_0_ring_get_rptr(struct amdgpu_ring *ring) static u64 mes_v12_0_ring_get_wptr(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -279,8 +282,8 @@ static int convert_to_mes_queue_type(int queue_type) else if (queue_type == AMDGPU_RING_TYPE_MES) return MES_QUEUE_TYPE_SCHQ; else - BUG(); - return -1; + WARN(1, "Invalid queue type %d\n", queue_type); + return MES_QUEUE_TYPE_GFX; } static int convert_to_mes_priority_level(int priority_level) -- cgit v1.2.3 From 14bcaa11c0304355ff04c06660ac16c1b7f66d24 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:28:47 -0400 Subject: drm/amdgpu/mes12.1: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_v12_1.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c b/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c index dbdb9ba02a5f..f1098118d5c0 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v12_1.c @@ -61,7 +61,7 @@ static void mes_v12_1_ring_set_wptr(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -72,12 +72,16 @@ static u64 mes_v12_1_ring_get_rptr(struct amdgpu_ring *ring) static u64 mes_v12_1_ring_get_wptr(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + + } return wptr; } @@ -278,8 +282,8 @@ static int convert_to_mes_queue_type(int queue_type) else if (queue_type == AMDGPU_RING_TYPE_MES) return MES_QUEUE_TYPE_SCHQ; else - BUG(); - return -1; + WARN(1, "Invalid queue type %d\n", queue_type); + return MES_QUEUE_TYPE_GFX; } static int mes_v12_1_add_hw_queue(struct amdgpu_mes *mes, -- cgit v1.2.3 From 6f0c77237cef64af825bf80fcfa3011c8ec720f3 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:32:46 -0400 Subject: drm/amdgpu/psp11: replace BUG() with an error There's no need to crash the kernel for this case. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/psp_v11_0.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v11_0.c b/drivers/gpu/drm/amd/amdgpu/psp_v11_0.c index 479690c44f0d..bd3bb3bfb668 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v11_0.c @@ -136,7 +136,9 @@ static int psp_v11_0_init_microcode(struct psp_context *psp) err = psp_init_toc_microcode(psp, ucode_prefix); break; default: - BUG(); + dev_warn(adev->dev, "Unsupported MP0 version 0x%08x\n", + amdgpu_ip_version(adev, MP0_HWIP, 0)); + return -EINVAL; } return err; -- cgit v1.2.3 From a09108c7046a00e31aeb4938f96a655ea10c1519 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:33:33 -0400 Subject: drm/amdgpu/psp13: replace BUG() with an error There's no need to crash the kernel for this case. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/psp_v13_0.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v13_0.c b/drivers/gpu/drm/amd/amdgpu/psp_v13_0.c index 00b4a34e6601..f9a3fbfc0634 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v13_0.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v13_0.c @@ -133,7 +133,9 @@ static int psp_v13_0_init_microcode(struct psp_context *psp) return err; break; default: - BUG(); + dev_warn(adev->dev, "Unsupported MP0 version 0x%08x\n", + amdgpu_ip_version(adev, MP0_HWIP, 0)); + return -EINVAL; } return 0; -- cgit v1.2.3 From 8a1484e134b1b548d46097dc3da861445b5c5d6e Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:33:49 -0400 Subject: drm/amdgpu/psp13.0.4: replace BUG() with an error There's no need to crash the kernel for this case. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/psp_v13_0_4.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v13_0_4.c b/drivers/gpu/drm/amd/amdgpu/psp_v13_0_4.c index 3d5e26b3fa00..e1acdbe01833 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v13_0_4.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v13_0_4.c @@ -50,7 +50,9 @@ static int psp_v13_0_4_init_microcode(struct psp_context *psp) return err; break; default: - BUG(); + dev_warn(adev->dev, "Unsupported MP0 version 0x%08x\n", + amdgpu_ip_version(adev, MP0_HWIP, 0)); + return -EINVAL; } return 0; -- cgit v1.2.3 From 8565fdaad0c5e4dca8dff76488c5f4bb809ef85b Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:34:09 -0400 Subject: drm/amdgpu/psp14: replace BUG() with an error There's no need to crash the kernel for this case. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/psp_v14_0.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/psp_v14_0.c b/drivers/gpu/drm/amd/amdgpu/psp_v14_0.c index 040a61aefa86..888c747d6c5c 100644 --- a/drivers/gpu/drm/amd/amdgpu/psp_v14_0.c +++ b/drivers/gpu/drm/amd/amdgpu/psp_v14_0.c @@ -85,7 +85,9 @@ static int psp_v14_0_init_microcode(struct psp_context *psp) return err; break; default: - BUG(); + dev_warn(adev->dev, "Unsupported MP0 version 0x%08x\n", + amdgpu_ip_version(adev, MP0_HWIP, 0)); + return -EINVAL; } return 0; -- cgit v1.2.3 From ea1b84c68aed446ffa61ab929a984fef4cf9adc9 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:36:45 -0400 Subject: drm/amdgpu/gfx6: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c index ac90d8e9d86a..cd2a14ecdba8 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c @@ -347,7 +347,8 @@ static int gfx_v6_0_init_microcode(struct amdgpu_device *adev) case CHIP_HAINAN: chip_name = "hainan"; break; - default: BUG(); + default: + return -EINVAL; } err = amdgpu_ucode_request(adev, &adev->gfx.pfp_fw, @@ -1736,8 +1737,8 @@ static void gfx_v6_0_constants_init(struct amdgpu_device *adev) gb_addr_config = HAINAN_GB_ADDR_CONFIG_GOLDEN; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported asic_type 0x%08x\n", adev->asic_type); + return; } WREG32(mmGRBM_CNTL, (0xff << GRBM_CNTL__READ_TIMEOUT__SHIFT)); @@ -2202,7 +2203,7 @@ static u64 gfx_v6_0_ring_get_wptr(struct amdgpu_ring *ring) else if (ring == &adev->gfx.compute_ring[1]) return RREG32(mmCP_RB2_WPTR); else - BUG(); + return 0; } static void gfx_v6_0_ring_set_wptr_gfx(struct amdgpu_ring *ring) @@ -2223,8 +2224,6 @@ static void gfx_v6_0_ring_set_wptr_compute(struct amdgpu_ring *ring) } else if (ring == &adev->gfx.compute_ring[1]) { WREG32(mmCP_RB2_WPTR, lower_32_bits(ring->wptr)); (void)RREG32(mmCP_RB2_WPTR); - } else { - BUG(); } } @@ -3310,7 +3309,6 @@ static void gfx_v6_0_set_compute_eop_interrupt_state(struct amdgpu_device *adev, } default: - BUG(); break; } -- cgit v1.2.3 From 48fd918301a30a6153b3659bd5a556840dffe5af Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:37:41 -0400 Subject: drm/amdgpu/gfx7: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c index 65b8497ad5f0..b93bad1d2a6f 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v7_0.c @@ -933,7 +933,8 @@ static int gfx_v7_0_init_microcode(struct amdgpu_device *adev) chip_name = "mullins"; break; default: - BUG(); + dev_warn(adev->dev, "Unsupported asic_type 0x%08x\n", adev->asic_type); + return -EINVAL; } err = amdgpu_ucode_request(adev, &adev->gfx.pfp_fw, -- cgit v1.2.3 From 51035c4eb236eecfbab8f1cc239c8d61ae10337b Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:38:20 -0400 Subject: drm/amdgpu/gfx8: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 9e0840df8849..6cf427995078 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -977,7 +977,8 @@ static int gfx_v8_0_init_microcode(struct amdgpu_device *adev) chip_name = "vegam"; break; default: - BUG(); + dev_warn(adev->dev, "Unsupported asic_type 0x%08x\n", adev->asic_type); + return -EINVAL; } if (adev->asic_type >= CHIP_POLARIS10 && adev->asic_type <= CHIP_POLARIS12) { -- cgit v1.2.3 From a9d5c19636aa6e0a1e9f2aab4cf7f012d0e971a8 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:41:44 -0400 Subject: drm/amdgpu/gfx9: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c index 1cad6dc9476e..c3322058720f 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c @@ -2107,8 +2107,9 @@ static int gfx_v9_0_gpu_early_init(struct amdgpu_device *adev) return err; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported GC version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return -EINVAL; } adev->gfx.config.gb_addr_config = gb_addr_config; @@ -5681,13 +5682,16 @@ static u64 gfx_v9_0_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v9_0_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -5699,8 +5703,8 @@ static void gfx_v9_0_ring_set_wptr_compute(struct amdgpu_ring *ring) if (ring->use_doorbell) { atomic64_set((atomic64_t *)ring->wptr_cpu_addr, ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); - } else{ - BUG(); /* only DOORBELL method supported on gfx9 now */ + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } -- cgit v1.2.3 From 62a2d2303ed22b519f28f5247be91a41dbefe6a9 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:51:49 -0400 Subject: drm/amdgpu/gfx9.4.3: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c index b086fabb8e1e..522981fa182d 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c @@ -3040,13 +3040,17 @@ static u64 gfx_v9_4_3_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v9_4_3_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)&ring->adev->wb.wb[ring->wptr_offs]); - else - BUG(); + } else { + dev_warn_once(adev->dev, + "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -3059,7 +3063,8 @@ static void gfx_v9_4_3_ring_set_wptr_compute(struct amdgpu_ring *ring) atomic64_set((atomic64_t *)&adev->wb.wb[ring->wptr_offs], ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); /* only DOORBELL method supported on gfx9 now */ + dev_warn_once(adev->dev, + "%s requires doorbell!\n", __func__); } } -- cgit v1.2.3 From a04c0c7e416490ee97af3b7768e6f21268fde54a Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:44:13 -0400 Subject: drm/amdgpu/gfx10: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c index a998ce77da40..00798d80479f 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c @@ -4580,7 +4580,7 @@ static const struct amdgpu_gfx_funcs gfx_v10_0_gfx_funcs = { static void gfx_v10_0_gpu_early_init(struct amdgpu_device *adev) { - u32 gb_addr_config; + u32 gb_addr_config = 0; switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { case IP_VERSION(10, 1, 10): @@ -4620,8 +4620,9 @@ static void gfx_v10_0_gpu_early_init(struct amdgpu_device *adev) gb_addr_config = CYAN_SKILLFISH_GB_ADDR_CONFIG_GOLDEN; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported GC version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return; } adev->gfx.config.gb_addr_config = gb_addr_config; @@ -8602,13 +8603,16 @@ static u64 gfx_v10_0_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v10_0_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -8621,7 +8625,7 @@ static void gfx_v10_0_ring_set_wptr_compute(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); /* only DOORBELL method supported on gfx10 now */ + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -9379,7 +9383,7 @@ static void gfx_v10_0_handle_priv_fault(struct amdgpu_device *adev, } break; default: - BUG(); + break; } } @@ -9449,7 +9453,7 @@ static int gfx_v10_0_kiq_set_interrupt_state(struct amdgpu_device *adev, } break; default: - BUG(); /* kiq only support GENERIC2_INT now */ + /* kiq only support GENERIC2_INT now */ break; } return 0; -- cgit v1.2.3 From 61f654bf337225bad9b44d00c91e4f5ea61c8c37 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:46:55 -0400 Subject: drm/amdgpu/gfx11: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 0cbbdc3694f4..3b203961abb9 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -1145,8 +1145,9 @@ static int gfx_v11_0_gpu_early_init(struct amdgpu_device *adev) adev->gfx.config.sc_earlyz_tile_fifo_size = 0x300; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported GC version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return -EINVAL; } return 0; @@ -5954,13 +5955,16 @@ static u64 gfx_v11_0_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v11_0_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -5974,7 +5978,7 @@ static void gfx_v11_0_ring_set_wptr_compute(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); /* only DOORBELL method supported on gfx11 now */ + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -6747,7 +6751,6 @@ static void gfx_v11_0_handle_priv_fault(struct amdgpu_device *adev, } break; default: - BUG(); break; } } @@ -6832,7 +6835,7 @@ static int gfx_v11_0_kiq_set_interrupt_state(struct amdgpu_device *adev, } break; default: - BUG(); /* kiq only support GENERIC2_INT now */ + /* kiq only support GENERIC2_INT now */ break; } return 0; -- cgit v1.2.3 From 836950e322aa5b2e06a331a78c3432ae99e80034 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:48:32 -0400 Subject: drm/amdgpu/gfx12: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index b3887c5262a0..5f0a24951f4a 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -964,8 +964,9 @@ static int gfx_v12_0_gpu_early_init(struct amdgpu_device *adev) adev->gfx.config.sc_earlyz_tile_fifo_size = 0x4C0; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported GC version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return -EINVAL; } return 0; @@ -4464,13 +4465,16 @@ static u64 gfx_v12_0_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v12_0_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -4484,7 +4488,7 @@ static void gfx_v12_0_ring_set_wptr_compute(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); /* only DOORBELL method supported on gfx12 now */ + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } @@ -5079,7 +5083,6 @@ static void gfx_v12_0_handle_priv_fault(struct amdgpu_device *adev, } break; default: - BUG(); break; } } -- cgit v1.2.3 From 3c2357bf9e7cc98b2c1fca161d8da6bda425c10f Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 15 Jun 2026 19:50:01 -0400 Subject: drm/amdgpu/gfx12.1: drop all BUG()s There's no need to crash the kernel for these cases. Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c index 1f7e243d2ad9..e49d8a79045c 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c @@ -891,8 +891,9 @@ static int gfx_v12_1_gpu_early_init(struct amdgpu_device *adev) adev->gfx.config.sc_earlyz_tile_fifo_size = 0x4C0; break; default: - BUG(); - break; + dev_warn(adev->dev, "Unsupported GC version 0x%08x\n", + amdgpu_ip_version(adev, GC_HWIP, 0)); + return -EINVAL; } return 0; @@ -3673,13 +3674,16 @@ static u64 gfx_v12_1_ring_get_rptr_compute(struct amdgpu_ring *ring) static u64 gfx_v12_1_ring_get_wptr_compute(struct amdgpu_ring *ring) { + struct amdgpu_device *adev = ring->adev; u64 wptr; /* XXX check if swapping is necessary on BE */ - if (ring->use_doorbell) + if (ring->use_doorbell) { wptr = atomic64_read((atomic64_t *)ring->wptr_cpu_addr); - else - BUG(); + } else { + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); + wptr = 0; + } return wptr; } @@ -3693,7 +3697,7 @@ static void gfx_v12_1_ring_set_wptr_compute(struct amdgpu_ring *ring) ring->wptr); WDOORBELL64(ring->doorbell_index, ring->wptr); } else { - BUG(); /* only DOORBELL method supported on gfx12 now */ + dev_warn_once(adev->dev, "%s requires doorbell!\n", __func__); } } -- cgit v1.2.3 From 6a03efb8613a6ffc23da42af41b3e51e975ea406 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 22 Jul 2026 18:59:22 +0200 Subject: drm/fourcc: Add modifiers for AMD GFX6-8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GFX6-8 are the oldest GPUs supported by the amdgpu kernel driver, and the last ones that didn't support DRM format modifiers until now. These are the Southern Islands, Sea Islands and Volcanic Islands families of GPUs. On GFX6-8, the GFX block can only use pre-determined tiling modes which are programmed by the kernel according to the tiling mode table. GFX6 uses the GB_TILE_MODE0...31 registers, and GFX7-8 also has GB_MACROTILE_MODE0...15 registers. DCC is also supported on GFX8, albeit not displayable. Note that the tiling table is uAPI and userspace relies on specific modes being present at specific indices. How the tiling works is primarily determined by the so-called array mode. Use the TILE field to specify the array mode. Pixel data is organized into micro tiles. Each micro tile may be 8x8 / 8x8x4 / 8x8x8 pixels, depending on the array mode. Add the MICROTILE field to specify microtile mode. Microtiles may be further organized into macro tiles, which have many configurable parameters. Macro tile mode selection depends on how many bits per pixel an image has. Add the PIPE_CONFIG, TILE_SPLIT, BANK_WIDTH, BANK_HEIGHT, MACRO_TILE_ASPECT, NUM_BANKS fields to specify parameters of macro tiled modes. Furthermore, tiling is also influenced by memory configuration. Old RFC patches received feedback concerning that, so I looked into it specifically: GB_ADDR_CONFIG.ROW_SIZE needs to be considered when calculating TILE_SPLIT, but does not need to be included in the modifiers, and also PIPE_INTERLEAVE matters, but it's hardcoded to the same value on all GFX6-8 GPUs and changing it would break userspace, so let's assume it isn't going to change. Therefore we don't need to include that in modifiers. Mesa also reads NUM_RANKS but actually doesn't use its value on GFX6-8. As a side note, tiling works similarly on GFX4-5 (that is Evergreen and Northern Islands). But that will need some additional PIPE_CONFIG enum values as well as some extra fields not relevant to GFX6-8. Initially, let's only expose the tiling modes that are most relevant to sharing buffers between different processes: Exposed array modes (TILE field): - 1D_TILED_THIN1: micro tiled only - 2D_TILED_THIN1: macro tiled Exposed micro tile modes (MICROTILE field): - DISPLAY: supported by DCE (the display engine) - THIN: more efficient but not displayable Exposed macro tile modes: All possible parameters (25088 permutations). More modes may be exposed in the future as needed. Technically, the amount of possible combinations of all possible tiling parameters is in the range of hundreds of thousands, but in practice, there are just a handful of possible modifiers for a surface. For example on GFX8, a surface would have these modifiers, from best to worst performance: - 2D_TILED_THIN1 + THIN + DCC + macrotile params [1] - 2D_TILED_THIN1 + THIN + macrotile params [1] - 2D_TILED_THIN1 + DISPLAY + macrotile params [1] - 1D_TILED_THIN1 + THIN - 1D_TILED_THIN1 + DISPLAY - LINEAR [1] The macro tiling parameters depend on how many bits per pixel of the specific surface has and how the chip is configured. There is only one set of valid macrotile params for a given surface. DCC is only supported by GFX8 and newer, and only with non-displayable macrotiling modes. When sharing buffers between different GFX6-8 GPUs, it is very unlikely that they support the exact same macrotiling configuration, so they will likely need to use micro tiled modes, which are still much better than using linear buffers. (Note that currently Mesa always uses LINEAR when copying between two GPUs.) Suggested-by: Bas Nieuwenhuizen Signed-off-by: Timur Kristóf Reviewed-by: Marek Olšák Reviewed-by: Daniel Stone Reviewed-by: Alex Deucher Acked-by: Christian König Signed-off-by: Alex Deucher --- include/uapi/drm/drm_fourcc.h | 209 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 191 insertions(+), 18 deletions(-) diff --git a/include/uapi/drm/drm_fourcc.h b/include/uapi/drm/drm_fourcc.h index 3a4d4dc635bf..a103fdc0b3a3 100644 --- a/include/uapi/drm/drm_fourcc.h +++ b/include/uapi/drm/drm_fourcc.h @@ -1646,36 +1646,69 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier) * For multi-plane formats the above surfaces get merged into one plane for * each format plane, based on the required alignment only. * - * Bits Parameter Notes - * ----- ------------------------ --------------------------------------------- + * Bits Parameter Notes + * ------- ------------------------ --------------------------------------------- + * + * DRM format modifier fields on AMD GPUs: + * 7:0 TILE_VERSION Values are AMD_FMT_MOD_TILE_VER_* + * 12:8 TILE Values are AMD_FMT_MOD_TILE__* + * 13 DCC Delta Color Compression, supported on GFX8 and newer + * 55:14 (chip specific) See below for details, depends on GFX block version + * 63:56 Vendor Value is DRM_FORMAT_MOD_VENDOR_AMD + * + * Chip specific fields on Gfx9 and newer: + * 14 DCC_RETILE + * 15 DCC_PIPE_ALIGN + * 16 DCC_INDEPENDENT_64B + * 17 DCC_INDEPENDENT_128B + * 19:18 DCC_MAX_COMPRESSED_BLOCK Values are AMD_FMT_MOD_DCC_BLOCK_* + * 20 DCC_CONSTANT_ENCODE + * 23:21 PIPE_XOR_BITS Only for some chips + * 26:24 BANK_XOR_BITS Only for some chips + * 29:27 PACKERS Only for some chips + * 32:30 RB Only for some chips + * 35:33 PIPE Only for some chips + * 55:36 - Reserved for future use, must be zero + * + * Chip specific fields on Gfx6-8: + * 16:14 MICROTILE Micro tile format + * 21:17 PIPE_CONFIG Number of pipes and how pipes are interleaved + * 24:22 TILE_SPLIT Tile split size + * 26:25 BANK_WIDTH Number of tiles in the X direction in the same bank + * 28:27 BANK_HEIGHT Number of tiles in the Y direction in the same bank + * 30:29 MACRO_TILE_ASPECT Macro tile aspect ratio + * 32:31 NUM_BANKS Number of banks + * 55:33 - Reserved for future use, must be zero * - * 7:0 TILE_VERSION Values are AMD_FMT_MOD_TILE_VER_* - * 12:8 TILE Values are AMD_FMT_MOD_TILE__* - * 13 DCC - * 14 DCC_RETILE - * 15 DCC_PIPE_ALIGN - * 16 DCC_INDEPENDENT_64B - * 17 DCC_INDEPENDENT_128B - * 19:18 DCC_MAX_COMPRESSED_BLOCK Values are AMD_FMT_MOD_DCC_BLOCK_* - * 20 DCC_CONSTANT_ENCODE - * 23:21 PIPE_XOR_BITS Only for some chips - * 26:24 BANK_XOR_BITS Only for some chips - * 29:27 PACKERS Only for some chips - * 32:30 RB Only for some chips - * 35:33 PIPE Only for some chips - * 55:36 - Reserved for future use, must be zero */ #define AMD_FMT_MOD fourcc_mod_code(AMD, 0) #define IS_AMD_FMT_MOD(val) (((val) >> 56) == DRM_FORMAT_MOD_VENDOR_AMD) -/* Reserve 0 for GFX8 and older */ +#define AMD_FMT_MOD_TILE_VER_GFX6 0 #define AMD_FMT_MOD_TILE_VER_GFX9 1 #define AMD_FMT_MOD_TILE_VER_GFX10 2 #define AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS 3 #define AMD_FMT_MOD_TILE_VER_GFX11 4 #define AMD_FMT_MOD_TILE_VER_GFX12 5 +/* + * Gfx6-8 tiling modes. + * A complete reference implementation is found in addrlib in the Mesa code base. + * + * - Microtiled modes (1D): + * Pixel data is organized into micro tiles of 8x8 pixels. + * + * - Macrotiled modes (2D): + * Micro tiles are further organized into macro tiles. + * These are optimized for even load distribution among memory channels. + * + * Note that only THIN1 modes are exposed here. + * THICK and XTHICK are for 3D images and not relevant to DRM format modifiers. + */ +#define AMD_FMT_MOD_TILE_GFX6_1D_TILED_THIN1 0x2 +#define AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1 0x4 + /* * 64K_S is the same for GFX9/GFX10/GFX10_RBPLUS and hence has GFX9 as canonical * version. @@ -1774,6 +1807,146 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier) #define AMD_FMT_MOD_PIPE_SHIFT 33 #define AMD_FMT_MOD_PIPE_MASK 0x7 +/* + * MICRO_TILE_MODE, 3 bits. Determines the micro tile format. + * Only relevant to Gfx6-8. + * + * DISPLAY - Displayable tiling + * THIN - Non-displayable tiling, a.k.a thin micro tiling + * DEPTH, THICK - not exposed, not relevant to DRM format modifier use cases + * ROTATED - not exposed, not implemented in Linux or Mesa + */ +#define AMD_FMT_MOD_MICROTILE_SHIFT 14ULL +#define AMD_FMT_MOD_MICROTILE_MASK 0x7 + +#define AMD_FMT_MOD_MICROTILE_DISPLAY 0x0 +#define AMD_FMT_MOD_MICROTILE_THIN 0x1 + +/* + * PIPE_CONFIG, 5 bits. Number of pipes and how pipes are interleaved on the surface, + * which means the shader engine tile size and packer tile size. + * Typically matches the number of memory channels, or number of RBs. + * Only relevant to Gfx6-8 macro tiled modes. + * + * P_x_x + * where: + * - number of pipes + * x - shader engine tile size + * x - packer tile size + */ +#define AMD_FMT_MOD_PIPE_CONFIG_SHIFT 17ULL +#define AMD_FMT_MOD_PIPE_CONFIG_MASK 0x1f + +#define AMD_FMT_MOD_PIPE_CONFIG_P2 0x0 +#define AMD_FMT_MOD_PIPE_CONFIG_P4_8x16 0x4 +#define AMD_FMT_MOD_PIPE_CONFIG_P4_16x16 0x5 +#define AMD_FMT_MOD_PIPE_CONFIG_P4_16x32 0x6 +#define AMD_FMT_MOD_PIPE_CONFIG_P4_32x32 0x7 +#define AMD_FMT_MOD_PIPE_CONFIG_P8_16x16_8x16 0x8 +#define AMD_FMT_MOD_PIPE_CONFIG_P8_16x32_8x16 0x9 +#define AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_8x16 0xa +#define AMD_FMT_MOD_PIPE_CONFIG_P8_16x32_16x16 0xb +#define AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_16x16 0xc +#define AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_16x32 0xd +#define AMD_FMT_MOD_PIPE_CONFIG_P8_32x64_32x32 0xe +#define AMD_FMT_MOD_PIPE_CONFIG_P16_32x32_8x16 0x10 +#define AMD_FMT_MOD_PIPE_CONFIG_P16_32x32_16x16 0x11 + +/* + * TILE_SPLIT, 3 bits. + * Only relevant to Gfx6-8 macro tiled modes. + * + * On GFX6 (or with depth tiling modes on GFX7 and newer), + * the GFX block uses the GB_TILE_MODE.TILE_SPLIT field directly. + * + * On GFX7 and newer with non-depth tiling modes, the GFX block uses a + * split factor which is stored in the GB_TILE_MODE.SAMPLE_SPLIT field. + * SAMPLE_SPLIT may be: 0 - 1 byte; 1 - 2 bytes; 2 - 4 bytes; 3 - 8 bytes. + * The actual tile size and tile split bytes are calculated as follows: + * + * bpp = ... <- bits per pixel in the current image + * thickness = ... <- depends on array mode; may be: 1, 4, 8 + * num_samples = ... <- number of samples in the current image + * tile_size_pixels = 8 * 8 + * tile_bytes_1x = thickness * tile_size_pixels * bpp / 8 + * sample_split_factor = 1 << SAMPLE_SPLIT + * tile_split_bytes = clamp(tile_bytes_1x * sample_split_factor, 256, dram_row_size_bytes) + * tile_bytes = clamp(tile_bytes_1x * num_samples, 64, tile_split_bytes) + * + * In both cases, the display block (DCE) has no SAMPLE_SPLIT + * and just needs the tile split bytes in the GRPH_CONTROL.GRPH_TILE_SPLIT field. + * To maximize compatibility between GFX6-7, we don't include the SAMPLE_SPLIT + * in the format modifiers. + * + * The actual tile split in bytes is: 64 << field value + * Possible values of this field: + * + * 0 - Tile split is 64 bytes + * 1 - Tile split is 128 bytes + * 2 - Tile split is 256 bytes + * 3 - Tile split is 512 bytes + * 4 - Tile split is 1 KiB + * 5 - Tile split is 2 KiB + * 6 - Tile split is 4 KiB + */ +#define AMD_FMT_MOD_TILE_SPLIT_SHIFT 22ULL +#define AMD_FMT_MOD_TILE_SPLIT_MASK 0x7 + +/* + * BANK_WIDTH, 2 bits. Number of tiles in the X direction in the same bank. + * Only relevant to Gfx6-8 macro tiled modes. + * The actual bank width is: 1 << field value + * Possible values: + * + * 0 - bank width is 1 + * 1 - bank width is 2 + * 2 - bank width is 4 + * 3 - bank width is 8 + */ +#define AMD_FMT_MOD_BANK_WIDTH_SHIFT 25ULL +#define AMD_FMT_MOD_BANK_WIDTH_MASK 0x3 + +/* + * BANK_HEIGHT, 2 bits. Number of tiles in the Y direction in the same bank. + * Only relevant to Gfx6-8 macro tiled modes. + * The actual bank height is: 1 << field value + * Possible values: + * + * 0 - bank height is 1 + * 1 - bank height is 2 + * 2 - bank height is 4 + * 3 - bank height is 8 + */ +#define AMD_FMT_MOD_BANK_HEIGHT_SHIFT 27ULL +#define AMD_FMT_MOD_BANK_HEIGHT_MASK 0x3 + +/* + * MACRO_TILE_ASPECT, 2 bits. Macro tile aspect ratio. + * Only relevant to Gfx6-8 macro tiled modes. + * Possible values: + * + * 0 - aspect ratio is 1:1 + * 1 - aspect ratio is 4:1 + * 2 - aspect ratio is 16:1 + * 3 - aspect ratio is 64:1 + */ +#define AMD_FMT_MOD_MACRO_TILE_ASPECT_SHIFT 29ULL +#define AMD_FMT_MOD_MACRO_TILE_ASPECT_MASK 0x3 + +/* + * NUM_BANKS, 2 bits. Number of banks. + * Only relevant to Gfx6-8 macro tiled modes. + * The actual number of banks is: 2 << field value + * Possible values: + * + * 0 - number of banks is 2 + * 1 - number of banks is 4 + * 2 - number of banks is 8 + * 3 - number of banks is 16 + */ +#define AMD_FMT_MOD_NUM_BANKS_SHIFT 31ULL +#define AMD_FMT_MOD_NUM_BANKS_MASK 0x3 + #define AMD_FMT_MOD_SET(field, value) \ ((__u64)(value) << AMD_FMT_MOD_##field##_SHIFT) #define AMD_FMT_MOD_GET(field, value) \ -- cgit v1.2.3 From 24c097b1123a29d6cee10c2c15cd4f52f870e83f Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 22 Jul 2026 18:59:23 +0200 Subject: drm/amdgpu: Convert tiling flags to modifiers on GFX6-8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is done for consistency between different GPU generations. Tiling flags are "implicit modifiers" which are used on AMD GPUs to let the kernel know the tiling information without modifiers. Convert the tiling flags to modifers on GFX8 and older, so that the DC display driver can rely on them like on newer generations. Note that this code path will only be taken when DC actually exposes any modifiers on GFX6-8, which is handled in a subsequent commit after this one. This code path mainly exists for legacy compositors which don't support explicit DRM format modifiers and rely on the tiling flags, and for compatibility with old Mesa that didn't support modifiers on GFX6-8. Add amdgpu_display_verify_sizes_gfx6() to validate modifiers on GFX6-8 and only accept the modes that the display driver really supports, as well as validate the FB sizes. Additionally adjust check_tiling_flags_gfx6() to allow the LINEAR_GENERAL mode and disallow the ROTATED mode which has never been supported by Mesa or Linux and was mistakenly allowed previously. Signed-off-by: Timur Kristóf Tested-by: Link Mauve Tested-by: Nikola Medić Tested-by: Dieter Nützel Reviewed-by: Marek Olšák Reviewed-by: Daniel Stone Reviewed-by: Alex Deucher Acked-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_display.c | 177 ++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c index f5cd68542442..59ea338b554c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c @@ -746,7 +746,7 @@ static int convert_tiling_flags_to_modifier_gfx12(struct amdgpu_framebuffer *afb return 0; } -static int convert_tiling_flags_to_modifier(struct amdgpu_framebuffer *afb) +static int convert_tiling_flags_to_modifier_gfx9(struct amdgpu_framebuffer *afb) { struct amdgpu_device *adev = drm_to_adev(afb->base.dev); uint64_t modifier = 0; @@ -940,25 +940,85 @@ static int convert_tiling_flags_to_modifier(struct amdgpu_framebuffer *afb) return 0; } -/* Mirrors the is_displayable check in radeonsi's gfx6_compute_surface */ +static int convert_tiling_flags_to_modifier_gfx6(struct amdgpu_framebuffer *afb) +{ + const uint32_t array_mode = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE); + const uint32_t pipe_config = AMDGPU_TILING_GET(afb->tiling_flags, PIPE_CONFIG); + const uint32_t tile_split = AMDGPU_TILING_GET(afb->tiling_flags, TILE_SPLIT); + const uint32_t micro_tile_mode = AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE); + const uint32_t bank_width = AMDGPU_TILING_GET(afb->tiling_flags, BANK_WIDTH); + const uint32_t bank_height = AMDGPU_TILING_GET(afb->tiling_flags, BANK_HEIGHT); + const uint32_t macro_tile_aspect = AMDGPU_TILING_GET(afb->tiling_flags, MACRO_TILE_ASPECT); + const uint32_t num_banks = AMDGPU_TILING_GET(afb->tiling_flags, NUM_BANKS); + struct amdgpu_device *adev = drm_to_adev(afb->base.dev); + uint64_t modifier = 0; + + switch (array_mode) { + case DC_ARRAY_LINEAR_GENERAL: + case DC_ARRAY_LINEAR_ALLIGNED: + modifier = DRM_FORMAT_MOD_LINEAR; + break; + + case DC_ARRAY_2D_TILED_THIN1: + /* Macro tiled modes only */ + modifier |= + AMD_FMT_MOD_SET(PIPE_CONFIG, pipe_config) | + AMD_FMT_MOD_SET(TILE_SPLIT, tile_split) | + AMD_FMT_MOD_SET(BANK_WIDTH, bank_width) | + AMD_FMT_MOD_SET(BANK_HEIGHT, bank_height) | + AMD_FMT_MOD_SET(MACRO_TILE_ASPECT, macro_tile_aspect) | + AMD_FMT_MOD_SET(NUM_BANKS, num_banks); + fallthrough; + + case DC_ARRAY_1D_TILED_THIN1: + /* Micro and macro tiled modes */ + modifier |= + AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX6) | + AMD_FMT_MOD_SET(TILE, array_mode) | + AMD_FMT_MOD_SET(MICROTILE, micro_tile_mode); + break; + + default: + drm_dbg_kms(&adev->ddev, "array mode 0x%x not supported\n", array_mode); + return -EINVAL; + } + + afb->base.modifier = modifier; + afb->base.flags |= DRM_MODE_FB_MODIFIERS; + return 0; +} + static int check_tiling_flags_gfx6(struct amdgpu_framebuffer *afb) { - u64 micro_tile_mode; + const u64 array_mode = AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE); + const u64 micro_tile_mode = AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE); - if (AMDGPU_TILING_GET(afb->tiling_flags, ARRAY_MODE) == 1) /* LINEAR_ALIGNED */ + /* LINEAR_GENERAL, LINEAR_ALIGNED */ + if (array_mode == 0 || array_mode == 1) return 0; - micro_tile_mode = AMDGPU_TILING_GET(afb->tiling_flags, MICRO_TILE_MODE); - switch (micro_tile_mode) { - case 0: /* DISPLAY */ - case 3: /* RENDER */ - return 0; - default: + /* 1D_TILED_THIN1, 2D_TILED_THIN1 */ + if (array_mode != 2 && array_mode != 4) { + drm_dbg_kms(afb->base.dev, + "Array mode %llu not supported for scanout\n", + array_mode); + return -EINVAL; + } + + /* + * For now, only allow DISPLAY micro tile mode. + * THIN, DEPTH and THICK modes are not displayable. + * ROTATED has never been supported by Linux or Mesa. + */ + if (micro_tile_mode != 0) { drm_dbg_kms(afb->base.dev, "Micro tile mode %llu not supported for scanout\n", micro_tile_mode); return -EINVAL; } + + return 0; } static void get_block_dimensions(unsigned int block_log2, unsigned int cpp, @@ -1053,6 +1113,85 @@ static int amdgpu_display_verify_plane(struct amdgpu_framebuffer *rfb, int plane return 0; } +static int amdgpu_display_verify_sizes_gfx6(struct drm_device *dev, + const u64 modifier, + unsigned int *out_block_width, + unsigned int *out_block_height) +{ + const u32 display_micro_tile_pitch = 32; /* required by DCE */ + const u32 micro_tile_width = 8; + const u32 micro_tile_height = 8; + const u32 micro_tile_mode = AMD_FMT_MOD_GET(MICROTILE, modifier); + const u32 array_mode = AMD_FMT_MOD_GET(TILE, modifier); + u32 num_banks, bank_width, bank_height, pipe_config, macro_tile_aspect; + u32 num_pipes; + + if (AMD_FMT_MOD_GET(DCC, modifier)) { + drm_dbg_kms(dev, "DCC is not displayable on GFX6-8\n"); + return -EINVAL; + } + if (array_mode != AMD_FMT_MOD_TILE_GFX6_1D_TILED_THIN1 && + array_mode != AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1) { + drm_dbg_kms(dev, "Array mode %u not supported for scanout\n", array_mode); + return -EINVAL; + } + if (micro_tile_mode != AMD_FMT_MOD_MICROTILE_DISPLAY) { + drm_dbg_kms(dev, "Microtile mode %u not supported for scanout\n", micro_tile_mode); + return -EINVAL; + } + + num_banks = 2 << AMD_FMT_MOD_GET(NUM_BANKS, modifier); + bank_width = 1 << AMD_FMT_MOD_GET(BANK_WIDTH, modifier); + bank_height = 1 << AMD_FMT_MOD_GET(BANK_HEIGHT, modifier); + pipe_config = AMD_FMT_MOD_GET(PIPE_CONFIG, modifier); + macro_tile_aspect = 1 << AMD_FMT_MOD_GET(MACRO_TILE_ASPECT, modifier); + + switch (pipe_config) { + case AMD_FMT_MOD_PIPE_CONFIG_P16_32x32_8x16: + case AMD_FMT_MOD_PIPE_CONFIG_P16_32x32_16x16: + num_pipes = 16; + break; + + case AMD_FMT_MOD_PIPE_CONFIG_P8_16x16_8x16: + case AMD_FMT_MOD_PIPE_CONFIG_P8_16x32_8x16: + case AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_8x16: + case AMD_FMT_MOD_PIPE_CONFIG_P8_16x32_16x16: + case AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_16x16: + case AMD_FMT_MOD_PIPE_CONFIG_P8_32x32_16x32: + case AMD_FMT_MOD_PIPE_CONFIG_P8_32x64_32x32: + num_pipes = 8; + break; + + case AMD_FMT_MOD_PIPE_CONFIG_P4_8x16: + case AMD_FMT_MOD_PIPE_CONFIG_P4_16x16: + case AMD_FMT_MOD_PIPE_CONFIG_P4_16x32: + case AMD_FMT_MOD_PIPE_CONFIG_P4_32x32: + num_pipes = 4; + break; + + case AMD_FMT_MOD_PIPE_CONFIG_P2: + num_pipes = 2; + break; + + default: + drm_dbg_kms(dev, "Pipe config %u invalid\n", pipe_config); + return -EINVAL; + } + + /* For reference, see SiLib::InitEquationTable() in addrlib */ + if (array_mode < AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1) { + *out_block_width = display_micro_tile_pitch; + *out_block_height = micro_tile_height; + } else { + /* Assume non-PRT macro tiling modes */ + *out_block_width = num_pipes * micro_tile_width * + bank_width * macro_tile_aspect; + *out_block_height = micro_tile_height * bank_height * + num_banks / macro_tile_aspect; + } + + return 0; +} static int amdgpu_display_verify_sizes(struct amdgpu_framebuffer *rfb) { @@ -1093,7 +1232,7 @@ static int amdgpu_display_verify_sizes(struct amdgpu_framebuffer *rfb) get_block_dimensions(block_size_log2, format_info->cpp[i], &block_width, &block_height); - } else { + } else if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) >= AMD_FMT_MOD_TILE_VER_GFX9) { int swizzle = AMD_FMT_MOD_GET(TILE, modifier); switch ((swizzle & ~3) + 1) { @@ -1120,6 +1259,18 @@ static int amdgpu_display_verify_sizes(struct amdgpu_framebuffer *rfb) get_block_dimensions(block_size_log2, format_info->cpp[i], &block_width, &block_height); + } else if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) == AMD_FMT_MOD_TILE_VER_GFX6) { + ret = amdgpu_display_verify_sizes_gfx6(rfb->base.dev, modifier, + &block_width, &block_height); + if (ret) + return ret; + + /* amdgpu_display_verify_plane() assumes the block size is a power of 2 */ + WARN_ON(!is_power_of_2(block_width)); + WARN_ON(!is_power_of_2(block_height)); + WARN_ON(!is_power_of_2(format_info->cpp[i])); + + block_size_log2 = ilog2(block_width * block_height * format_info->cpp[i]); } ret = amdgpu_display_verify_plane(rfb, i, format_info, @@ -1271,8 +1422,10 @@ static int amdgpu_display_framebuffer_init(struct drm_device *dev, !(rfb->base.flags & DRM_MODE_FB_MODIFIERS)) { if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(12, 0, 0)) ret = convert_tiling_flags_to_modifier_gfx12(rfb); + else if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(9, 0, 0)) + ret = convert_tiling_flags_to_modifier_gfx9(rfb); else - ret = convert_tiling_flags_to_modifier(rfb); + ret = convert_tiling_flags_to_modifier_gfx6(rfb); if (ret) { drm_dbg_kms(dev, "Failed to convert tiling flags 0x%llX to a modifier", -- cgit v1.2.3 From 3104069690c499ffa1fc3de998c6d0acf24ee350 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 22 Jul 2026 18:59:24 +0200 Subject: drm/amd/display: Support DRM format modifiers on GFX6-8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose displayable DRM format modifiers based on which tiling modes are supported by the GFX block. Technically, DCE (Display Controller Engine) could support all possible permutations of all parameters independently of what GFX supports, with the limitation that it can only display LINEAR images and the displayable micro tiling mode (MICROTILE == DISPLAY). It doesn't make sense to expose all possible permutations of macro tiling modes, so let's just expose what the GFX block of the current chip supports. The following modes will be advertised: - 2D_TILED_THIN1 + DISPLAY + macrotile params [1] - 1D_TILED_THIN1 + DISPLAY - LINEAR [1] The macro tiling parameters depend on how many bits per pixel of the specific surface has and how the chip is configured. There is only one set of valid macrotile params for a given surface. Signed-off-by: Timur Kristóf Tested-by: Link Mauve Tested-by: Nikola Medić Tested-by: Dieter Nützel Reviewed-by: Marek Olšák Reviewed-by: Daniel Stone Reviewed-by: Alex Deucher Acked-by: Christian König Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 322 ++++++++++++++++++++- .../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h | 4 + .../display/amdgpu_dm/tests/amdgpu_dm_plane_test.c | 6 - 3 files changed, 319 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index e43fce16b9ff..bad98f2c5cc6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -180,6 +180,22 @@ STATIC_IFN_KUNIT void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *si } EXPORT_IF_KUNIT(amdgpu_dm_plane_add_modifier); +STATIC_IFN_KUNIT void amdgpu_dm_plane_add_modifier_dedup(uint64_t **mods, uint64_t *size, + uint64_t *cap, uint64_t mod) +{ + uint64_t i; + + if (!*mods) + return; + + for (i = 0; i < *size; ++i) + if ((*mods)[i] == mod) + return; + + amdgpu_dm_plane_add_modifier(mods, size, cap, mod); +} +EXPORT_IF_KUNIT(amdgpu_dm_plane_add_modifier_dedup); + STATIC_IFN_KUNIT bool amdgpu_dm_plane_modifier_has_dcc(uint64_t modifier) { return IS_AMD_FMT_MOD(modifier) && AMD_FMT_MOD_GET(DCC, modifier); @@ -228,6 +244,41 @@ STATIC_IFN_KUNIT void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc } EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags); +STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier( + struct dc_tiling_info *tiling_info, + uint64_t modifier) +{ + if (modifier == DRM_FORMAT_MOD_LINEAR) { + tiling_info->gfx8.array_mode = DC_ARRAY_LINEAR_GENERAL; + tiling_info->gfxversion = DcGfxVersion8; + + return 0; + } + + if (!IS_AMD_FMT_MOD(modifier)) + return -EINVAL; + + if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) != AMD_FMT_MOD_TILE_VER_GFX6) + return -EINVAL; + + tiling_info->gfx8.array_mode = AMD_FMT_MOD_GET(TILE, modifier); + tiling_info->gfxversion = DcGfxVersion8; + tiling_info->gfx8.tile_mode = AMD_FMT_MOD_GET(MICROTILE, modifier); + + if (AMD_FMT_MOD_GET(TILE, modifier) < AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1) + return 0; + + tiling_info->gfx8.pipe_config = AMD_FMT_MOD_GET(PIPE_CONFIG, modifier); + tiling_info->gfx8.tile_split = AMD_FMT_MOD_GET(TILE_SPLIT, modifier); + tiling_info->gfx8.bank_width = AMD_FMT_MOD_GET(BANK_WIDTH, modifier); + tiling_info->gfx8.bank_height = AMD_FMT_MOD_GET(BANK_HEIGHT, modifier); + tiling_info->gfx8.tile_aspect = AMD_FMT_MOD_GET(MACRO_TILE_ASPECT, modifier); + tiling_info->gfx8.num_banks = AMD_FMT_MOD_GET(NUM_BANKS, modifier); + + return 0; +} +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier); + STATIC_IFN_KUNIT void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgpu_device *adev, struct dc_tiling_info *tiling_info) { @@ -468,6 +519,249 @@ static void amdgpu_dm_plane_add_gfx10_1_modifiers(const struct amdgpu_device *ad AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9)); } +/** + * amdgpu_dm_plane_get_gfx6_tile_idx() - Get tile mode index on GFX6-8 + * + * @adev: amdgpu_device pointer + * @bpp: bits per pixel in the image + * @arr: array mode (aka. tile mode) of the image layout + * + * Select which tiling mode from the table is suitable for the given bits per pixel + * using the given array mode, assuming the displayable micro tile mode is used. + * + * On GFX6-8, the GFX block can only use pre-programmed tiling modes from GB_TILE_MODEn + * registers which are programmed by the kernel according to the tiling mode table. + * Note that the tiling table is uAPI and userspace relies on specific modes being + * present at specific indices. + * See SiLib::HwlSetupTileInfo() and CiLib::HwlSetupTileInfo() in addrlib. + */ +static u32 amdgpu_dm_plane_get_gfx6_tile_idx(const struct amdgpu_device *adev, + const u32 bpp, + const enum array_mode_values arr) +{ + /* Assume that the microtile mode is DISPLAY. */ + + if (arr == DC_ARRAY_1D_TILED_THIN1) + return 9; + + ASSERT(arr == DC_ARRAY_2D_TILED_THIN1); + + if (adev->family > AMDGPU_FAMILY_SI) + return 10; + + switch (bpp) { + case 8: + return 10; + case 16: + return 11; + default: + return 12; + } +} + +/** + * amdgpu_dm_plane_calc_gfx7_tile_split() - Calculate tile split on GFX7-8 + * + * @adev: amdgpu_device pointer + * @bpp: bits per pixel in the image + * @gb_tile_mode: GB_TILE_MODEn register value for the current tiling mode + * + * Calculate the actual tile split value on GFX7-8, assuming 2D_TILED_THIN1 array mode + * with a non-depth micro tile mode. + * + * On GFX7-8, SAMPLE_SPLIT holds a factor from which the actual tile split bytes + * can be calculated. The TILE_SPLIT field is only used for the depth micro tile mode. + * See CiLib::HwlComputeMacroModeIndex() in addrlib. + */ +static u32 amdgpu_dm_plane_calc_gfx7_tile_split(const struct amdgpu_device *adev, + const u32 bpp, + const u32 gb_tile_mode) +{ + /* Assume 2D_TILED_THIN1 mode with non-DEPTH microtiles */ + const u32 sample_split = (gb_tile_mode >> 25) & 0x3; + const u32 thickness = 1; + const u32 tile_size_pixels = 8 * 8; + const u32 tile_bytes_1x = tile_size_pixels * bpp * thickness / 8; + const u32 sample_split_factor = 1 << sample_split; + + return clamp(tile_bytes_1x * sample_split_factor, + 256, + adev->gfx.config.mem_row_size_in_kb * 1024); +} + +/** + * amdgpu_dm_plane_get_gfx7_macro_tile_idx() - Get macro tile mode index on GFX7-8 + * + * @bpp: bits per pixel in the image + * @tile_split_bytes: actual tile split bytes, see amdgpu_dm_plane_calc_gfx7_tile_split() + * + * Select which macro tiling mode from the table is suitable for the given bits per pixel, + * assuming 2D_TILED_THIN1 array mode and DISPLAY micro tile mode (and no multisampling). + * Note that the tiling table is uAPI and userspace relies on specific modes being + * present at specific indices. + * See CiLib::HwlComputeMacroModeIndex() in addrlib. + */ +static u32 amdgpu_dm_plane_get_gfx7_macro_tile_idx(const u32 bpp, const u32 tile_split_bytes) +{ + const u32 thickness = 1; + const u32 tile_size_pixels = 8 * 8; + const u32 tile_bytes_1x = thickness * tile_size_pixels * bpp / 8; + const u32 tile_bytes = clamp(tile_bytes_1x, 64, tile_split_bytes); + const u32 macro_tile_idx = ilog2(tile_bytes / 64); + + WARN_ON(macro_tile_idx >= 16); + + return macro_tile_idx; +} + +/** + * amdgpu_dm_plane_calc_gfx6_mod() - Calculate a DRM format modifier for GFX6-8 + * + * @adev: amdgpu_device pointer + * @bpp: bits per pixel in the image + * @arr: array mode (aka. tile mode) of the image layout + * + * Select suitable micro and macro tile modes for the given bits per pixel, + * and calculate the corresponding DRM format modifier. + */ +static u64 amdgpu_dm_plane_calc_gfx6_mod(const struct amdgpu_device *adev, + const u32 bpp, + const enum array_mode_values arr) +{ + u32 array_mode, micro_tile_mode, tile_split_bytes; + u32 gb_macrotile_mode, macrotile_idx; + u32 gb_tile_mode, tile_idx; + + u64 modifier_base = + AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX6) | + AMD_FMT_MOD_SET(TILE, arr) | + AMD_FMT_MOD_SET(MICROTILE, AMD_FMT_MOD_MICROTILE_DISPLAY); + + if (arr < DC_ARRAY_2D_TILED_THIN1) + return modifier_base; + + tile_idx = amdgpu_dm_plane_get_gfx6_tile_idx(adev, bpp, arr); + gb_tile_mode = adev->gfx.config.tile_mode_array[tile_idx]; + array_mode = (gb_tile_mode >> 2) & 0xf; + + if (adev->family == AMDGPU_FAMILY_SI) { + micro_tile_mode = (gb_tile_mode >> 0) & 0x3; + tile_split_bytes = 64 << ((gb_tile_mode >> 11) & 0x7); + } else { + micro_tile_mode = (gb_tile_mode >> 22) & 0x7; + tile_split_bytes = amdgpu_dm_plane_calc_gfx7_tile_split(adev, bpp, gb_tile_mode); + } + + ASSERT(array_mode == arr); + ASSERT(micro_tile_mode == AMD_FMT_MOD_MICROTILE_DISPLAY); + + modifier_base |= + AMD_FMT_MOD_SET(PIPE_CONFIG, (gb_tile_mode >> 6) & 0x1f) | + AMD_FMT_MOD_SET(TILE_SPLIT, ilog2(tile_split_bytes / 64)); + + if (adev->family == AMDGPU_FAMILY_SI) + return modifier_base | + AMD_FMT_MOD_SET(BANK_WIDTH, (gb_tile_mode >> 14) & 0x3) | + AMD_FMT_MOD_SET(BANK_HEIGHT, (gb_tile_mode >> 16) & 0x3) | + AMD_FMT_MOD_SET(MACRO_TILE_ASPECT, (gb_tile_mode >> 18) & 0x3) | + AMD_FMT_MOD_SET(NUM_BANKS, (gb_tile_mode >> 20) & 0x3); + + macrotile_idx = amdgpu_dm_plane_get_gfx7_macro_tile_idx(bpp, tile_split_bytes); + gb_macrotile_mode = adev->gfx.config.macrotile_mode_array[macrotile_idx]; + + return modifier_base | + AMD_FMT_MOD_SET(BANK_WIDTH, (gb_macrotile_mode >> 0) & 0x3) | + AMD_FMT_MOD_SET(BANK_HEIGHT, (gb_macrotile_mode >> 2) & 0x3) | + AMD_FMT_MOD_SET(MACRO_TILE_ASPECT, (gb_macrotile_mode >> 4) & 0x3) | + AMD_FMT_MOD_SET(NUM_BANKS, (gb_macrotile_mode >> 6) & 0x3); +} + +/** + * amdgpu_dm_plane_gfx6_format_mod_supported() - Check if a modifier is supported on GFX6-8 + * + * @adev: amdgpu_device pointer + * @bpp: bits per pixel in the image + * @modifier: the modifier whose support we check + * + * On GFX6-8, not all DRM format modifier can be used with all image formats. + * Check whether the specified modifier is supported with the given bits per pixel value. + */ +static bool amdgpu_dm_plane_gfx6_format_mod_supported(const struct amdgpu_device *adev, + const u32 bpp, + const u64 modifier) +{ + const u32 array_mode = AMD_FMT_MOD_GET(TILE, modifier); + const u32 micro_tile_mode = AMD_FMT_MOD_GET(MICROTILE, modifier); + + if (!IS_AMD_FMT_MOD(modifier)) + return false; + + /* GFX9 and newer format modifiers are not supported on GFX6-8. */ + if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) != AMD_FMT_MOD_TILE_VER_GFX6) + return false; + + /* GFX8 and older format modifiers are not supported on GFX9+. */ + if (adev->family >= AMDGPU_FAMILY_AI) + return false; + + /* GFX6-7 doesn't have any DCC, GFX8 doesn't support displayable DCC. */ + if (AMD_FMT_MOD_GET(DCC, modifier)) + return false; + + /* + * For now, only expose 1D and 2D tiles THIN array modes. + * Linear is already exposed through DRM_FORMAT_MOD_LINEAR. + */ + if (array_mode != AMD_FMT_MOD_TILE_GFX6_1D_TILED_THIN1 && + array_mode != AMD_FMT_MOD_TILE_GFX6_2D_TILED_THIN1) + return false; + + /* + * For now, only allow DISPLAY micro tile mode. + * THIN, DEPTH and THICK modes are not displayable. + * ROTATED has never been supported by Linux or Mesa. + */ + if (micro_tile_mode != AMD_FMT_MOD_MICROTILE_DISPLAY) + return false; + + /* Verify that the modifier is the same that we'd expose for this bpp */ + return amdgpu_dm_plane_calc_gfx6_mod(adev, bpp, array_mode) == modifier; +} + +/** + * amdgpu_dm_plane_add_gfx6_modifiers() - Expose modifiers for GFX6-8 + * + * @adev: amdgpu_device pointer + * @mods: Pointer to array of format modifiers + * @size: Pointer to size of the array + * @capacity: Pointer to capacity of the array + * + * Calculate a DRM format modifier for macro tiled modes for each supported + * bits per pixel value. Use de-duplication because on some GPUs it may happen + * that different bpp results in the exact same macro tiling mode, depending + * on the tiling table. + * + * Also expose a micro tiled only mode. This is less optimal, but supported. + */ +static void amdgpu_dm_plane_add_gfx6_modifiers(const struct amdgpu_device *adev, + u64 **mods, + u64 *size, + u64 *capacity) +{ + /* 2D tiled displayable */ + amdgpu_dm_plane_add_modifier(mods, size, capacity, + amdgpu_dm_plane_calc_gfx6_mod(adev, 16, DC_ARRAY_2D_TILED_THIN1)); + amdgpu_dm_plane_add_modifier_dedup(mods, size, capacity, + amdgpu_dm_plane_calc_gfx6_mod(adev, 32, DC_ARRAY_2D_TILED_THIN1)); + amdgpu_dm_plane_add_modifier_dedup(mods, size, capacity, + amdgpu_dm_plane_calc_gfx6_mod(adev, 64, DC_ARRAY_2D_TILED_THIN1)); + + /* 1D tiled displayable */ + amdgpu_dm_plane_add_modifier(mods, size, capacity, + amdgpu_dm_plane_calc_gfx6_mod(adev, 0, DC_ARRAY_1D_TILED_THIN1)); +} + static void amdgpu_dm_plane_add_gfx9_modifiers(const struct amdgpu_device *adev, uint64_t **mods, uint64_t *size, @@ -749,12 +1043,6 @@ static void amdgpu_dm_plane_add_gfx12_modifiers(struct amdgpu_device *adev, STATIC_IFN_KUNIT int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, unsigned int plane_type, uint64_t **mods) { uint64_t size = 0, capacity = 128; - *mods = NULL; - - /* We have not hooked up any pre-GFX9 modifiers. */ - if (adev->family < AMDGPU_FAMILY_AI) - return 0; - *mods = kmalloc_array(capacity, sizeof(uint64_t), GFP_KERNEL); if (plane_type == DRM_PLANE_TYPE_CURSOR) { @@ -764,6 +1052,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *a } switch (adev->family) { + case AMDGPU_FAMILY_SI: + case AMDGPU_FAMILY_CI: + case AMDGPU_FAMILY_KV: + case AMDGPU_FAMILY_VI: + case AMDGPU_FAMILY_CZ: + amdgpu_dm_plane_add_gfx6_modifiers(adev, mods, &size, &capacity); + break; case AMDGPU_FAMILY_AI: case AMDGPU_FAMILY_RV: amdgpu_dm_plane_add_gfx9_modifiers(adev, mods, &size, &capacity); @@ -940,8 +1235,13 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, address); if (ret) return ret; - } else { + } else if (afb->base.modifier == DRM_FORMAT_MOD_INVALID) { amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(tiling_info, tiling_flags); + } else { + ret = amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier(tiling_info, + afb->base.modifier); + if (ret) + return ret; } return 0; @@ -1587,6 +1887,7 @@ STATIC_IFN_KUNIT bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *pla { struct amdgpu_device *adev = drm_to_adev(plane->dev); const struct drm_format_info *info = drm_format_info(format); + const u32 bpp = drm_format_info_bpp(info, 0); int i; if (!info) @@ -1610,6 +1911,13 @@ STATIC_IFN_KUNIT bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *pla if (i == plane->modifier_count) return false; + if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) == AMD_FMT_MOD_TILE_VER_GFX6) + return amdgpu_dm_plane_gfx6_format_mod_supported(adev, bpp, modifier); + + /* GFX9+ modifers are not supported on GFX8 and older. */ + if (adev->family < AMDGPU_FAMILY_AI) + return false; + /* GFX12 doesn't have these limitations. */ if (AMD_FMT_MOD_GET(TILE_VERSION, modifier) <= AMD_FMT_MOD_TILE_VER_GFX11) { enum dm_micro_swizzle microtile = amdgpu_dm_plane_modifier_gfx9_swizzle_mode(modifier) & 3; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h index 365e306a52e7..9d10ad61f64d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h @@ -124,5 +124,9 @@ void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane); struct drm_plane_state *amdgpu_dm_plane_drm_plane_duplicate_state(struct drm_plane *plane); void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, struct drm_plane_state *state); +void amdgpu_dm_plane_add_modifier_dedup(uint64_t **mods, uint64_t *size, + uint64_t *cap, uint64_t mod); +int amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier(struct dc_tiling_info *tiling_info, + uint64_t modifier); #endif #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index a751e67cadf1..c521960e5ca8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -328,12 +328,6 @@ static void dm_test_get_plane_modifiers(struct kunit *test) adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, adev); - adev->family = AMDGPU_FAMILY_SI; - KUNIT_EXPECT_EQ(test, - amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_PRIMARY, &mods), - 0); - KUNIT_EXPECT_PTR_EQ(test, mods, NULL); - adev->family = AMDGPU_FAMILY_NV; KUNIT_ASSERT_EQ(test, amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_CURSOR, &mods), -- cgit v1.2.3 From 1a16dfe5359f148d92f3759674364f6b00a6d41d Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 22 Jul 2026 18:59:25 +0200 Subject: drm/amd/display: Don't use tiling flags anymore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All supported GPU generations now support DRM format modifiers. Remove all code from amdgpu_dm that dealt with tiling flags. Note that the legacy non-DC display code still relies on tiling flags, so we can't remove them outside of DC until we also remove the legacy display code. Signed-off-by: Timur Kristóf Tested-by: Link Mauve Tested-by: Nikola Medić Tested-by: Dieter Nützel Reviewed-by: Marek Olšák Reviewed-by: Daniel Stone Reviewed-by: Alex Deucher Acked-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 8 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 37 ------ .../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h | 3 - .../display/amdgpu_dm/tests/amdgpu_dm_plane_test.c | 143 +-------------------- 4 files changed, 3 insertions(+), 188 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 979149ded1c2..f22388e57e98 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -2917,7 +2917,6 @@ EXPORT_IF_KUNIT(fill_plane_color_attributes); static int fill_dc_plane_info_and_addr(struct amdgpu_device *adev, const struct drm_plane_state *plane_state, - const u64 tiling_flags, struct dc_plane_info *plane_info, struct dc_plane_address *address, bool tmz_surface) @@ -3015,7 +3014,7 @@ fill_dc_plane_info_and_addr(struct amdgpu_device *adev, return ret; ret = amdgpu_dm_plane_fill_plane_buffer_attributes(adev, afb, plane_info->format, - plane_info->rotation, tiling_flags, + plane_info->rotation, &plane_info->tiling_info, &plane_info->plane_size, &plane_info->dcc, address, @@ -3051,7 +3050,6 @@ static int fill_dc_plane_attributes(struct amdgpu_device *adev, dc_plane_state->scaling_quality = scaling_info.scaling_quality; ret = fill_dc_plane_info_and_addr(adev, plane_state, - afb->tiling_flags, &plane_info, &dc_plane_state->address, afb->tmz_surface); @@ -3878,7 +3876,6 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, fill_dc_plane_info_and_addr( dm->adev, new_plane_state, - afb->tiling_flags, &bundle->plane_infos[planes_count], &bundle->flip_addrs[planes_count].address, afb->tmz_surface); @@ -5751,8 +5748,7 @@ static bool should_reset_plane(struct drm_atomic_commit *state, new_afb = (struct amdgpu_framebuffer *)new_other_state->fb; /* Tiling and DCC changes also require bandwidth updates. */ - if (old_afb->tiling_flags != new_afb->tiling_flags || - old_afb->base.modifier != new_afb->base.modifier) + if (old_afb->base.modifier != new_afb->base.modifier) return true; } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index bad98f2c5cc6..0c6f0cac666e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -211,39 +211,6 @@ STATIC_IFN_KUNIT unsigned int amdgpu_dm_plane_modifier_gfx9_swizzle_mode(uint64_ } EXPORT_IF_KUNIT(amdgpu_dm_plane_modifier_gfx9_swizzle_mode); -STATIC_IFN_KUNIT void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc_tiling_info *tiling_info, - uint64_t tiling_flags) -{ - /* Fill GFX8 params */ - if (AMDGPU_TILING_GET(tiling_flags, ARRAY_MODE) == DC_ARRAY_2D_TILED_THIN1) { - unsigned int bankw, bankh, mtaspect, tile_split, num_banks; - bankw = AMDGPU_TILING_GET(tiling_flags, BANK_WIDTH); - bankh = AMDGPU_TILING_GET(tiling_flags, BANK_HEIGHT); - mtaspect = AMDGPU_TILING_GET(tiling_flags, MACRO_TILE_ASPECT); - tile_split = AMDGPU_TILING_GET(tiling_flags, TILE_SPLIT); - num_banks = AMDGPU_TILING_GET(tiling_flags, NUM_BANKS); - - tiling_info->gfxversion = DcGfxVersion8; - /* XXX fix me for VI */ - tiling_info->gfx8.num_banks = num_banks; - tiling_info->gfx8.array_mode = - DC_ARRAY_2D_TILED_THIN1; - tiling_info->gfx8.tile_split = tile_split; - tiling_info->gfx8.bank_width = bankw; - tiling_info->gfx8.bank_height = bankh; - tiling_info->gfx8.tile_aspect = mtaspect; - tiling_info->gfx8.tile_mode = - DC_ADDR_SURF_MICRO_TILING_DISPLAY; - } else if (AMDGPU_TILING_GET(tiling_flags, ARRAY_MODE) - == DC_ARRAY_1D_TILED_THIN1) { - tiling_info->gfx8.array_mode = DC_ARRAY_1D_TILED_THIN1; - } - - tiling_info->gfx8.pipe_config = - AMDGPU_TILING_GET(tiling_flags, PIPE_CONFIG); -} -EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags); - STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier( struct dc_tiling_info *tiling_info, uint64_t modifier) @@ -1160,7 +1127,6 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, - const uint64_t tiling_flags, struct dc_tiling_info *tiling_info, struct plane_size *plane_size, struct dc_plane_dcc_param *dcc, @@ -1235,8 +1201,6 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, address); if (ret) return ret; - } else if (afb->base.modifier == DRM_FORMAT_MOD_INVALID) { - amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(tiling_info, tiling_flags); } else { ret = amdgpu_dm_plane_fill_gfx6_tiling_info_from_modifier(tiling_info, afb->base.modifier); @@ -1332,7 +1296,6 @@ static int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane, amdgpu_dm_plane_fill_plane_buffer_attributes( adev, afb, plane_state->format, plane_state->rotation, - afb->tiling_flags, &plane_state->tiling_info, &plane_state->plane_size, &plane_state->dcc, &plane_state->address, afb->tmz_surface); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h index 9d10ad61f64d..092ade738ce9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h @@ -48,7 +48,6 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, const enum surface_pixel_format format, const enum dc_rotation_angle rotation, - const uint64_t tiling_flags, struct dc_tiling_info *tiling_info, struct plane_size *plane_size, struct dc_plane_dcc_param *dcc, @@ -71,8 +70,6 @@ bool amdgpu_dm_plane_is_video_format(uint32_t format); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *size, uint64_t *cap, uint64_t mod); -void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc_tiling_info *tiling_info, - uint64_t tiling_flags); void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgpu_device *adev, struct dc_tiling_info *tiling_info); void amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(const struct amdgpu_device *adev, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index c521960e5ca8..ba97092c7bb8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -406,62 +406,6 @@ static void dm_test_get_min_max_dc_plane_scaling(struct kunit *test) KUNIT_EXPECT_EQ(test, max_upscale, 1600); } -/** - * dm_test_fill_plane_buffer_attributes_gfx8() - Verify graphics path and GFX8 tiling fill. - * @test: KUnit test context. - * - * Verify if GFX8 plane buffer attributes and tiling fields are filled correctly. - */ -static void dm_test_fill_plane_buffer_attributes_gfx8(struct kunit *test) -{ - struct amdgpu_device *adev; - struct amdgpu_framebuffer *afb; - struct dc_tiling_info *tiling_info; - struct plane_size *plane_size; - struct dc_plane_dcc_param *dcc; - struct dc_plane_address *address; - uint64_t tiling_flags = 0; - int ret; - - adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); - tiling_info = kunit_kzalloc(test, sizeof(*tiling_info), GFP_KERNEL); - plane_size = kunit_kzalloc(test, sizeof(*plane_size), GFP_KERNEL); - dcc = kunit_kzalloc(test, sizeof(*dcc), GFP_KERNEL); - address = kunit_kzalloc(test, sizeof(*address), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, adev); - KUNIT_ASSERT_NOT_NULL(test, afb); - KUNIT_ASSERT_NOT_NULL(test, tiling_info); - KUNIT_ASSERT_NOT_NULL(test, plane_size); - KUNIT_ASSERT_NOT_NULL(test, dcc); - KUNIT_ASSERT_NOT_NULL(test, address); - - adev->family = AMDGPU_FAMILY_SI; - afb->address = 0x12345000ULL; - afb->base.width = 1920; - afb->base.height = 1080; - afb->base.offsets[0] = 0x1000; - afb->base.pitches[0] = 7680; - afb->base.format = drm_format_info(DRM_FORMAT_XRGB8888); - KUNIT_ASSERT_NOT_NULL(test, afb->base.format); - - tiling_flags |= AMDGPU_TILING_SET(ARRAY_MODE, DC_ARRAY_1D_TILED_THIN1); - tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 5); - - ret = amdgpu_dm_plane_fill_plane_buffer_attributes(adev, afb, - SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, - tiling_flags, tiling_info, plane_size, dcc, address, true); - - KUNIT_EXPECT_EQ(test, ret, 0); - KUNIT_EXPECT_EQ(test, plane_size->surface_size.width, 1920); - KUNIT_EXPECT_EQ(test, plane_size->surface_size.height, 1080); - KUNIT_EXPECT_EQ(test, plane_size->surface_pitch, 1920); - KUNIT_EXPECT_EQ(test, address->type, (int)PLN_ADDR_TYPE_GRAPHICS); - KUNIT_EXPECT_TRUE(test, address->tmz_surface); - KUNIT_EXPECT_EQ(test, (int)tiling_info->gfx8.array_mode, (int)DC_ARRAY_1D_TILED_THIN1); - KUNIT_EXPECT_EQ(test, tiling_info->gfx8.pipe_config, 5U); -} - /** * dm_test_get_cursor_position() - Verify cursor clipping and off-screen handling. * @test: KUnit test context. @@ -992,86 +936,6 @@ static void dm_test_add_modifier_noop_when_mods_null(struct kunit *test) KUNIT_EXPECT_EQ(test, cap, 7ULL); } -/** - * dm_test_fill_gfx8_tiling_info_2d_tiled() - Verify GFX8 2D tiled flag parsing. - * @test: KUnit test context. - * - * Verify if 2D tiled GFX8 flags populate expected tiling fields. - */ -static void dm_test_fill_gfx8_tiling_info_2d_tiled(struct kunit *test) -{ - struct dc_tiling_info tiling_info = {0}; - uint64_t tiling_flags = 0; - - tiling_flags |= AMDGPU_TILING_SET(ARRAY_MODE, DC_ARRAY_2D_TILED_THIN1); - tiling_flags |= AMDGPU_TILING_SET(BANK_WIDTH, 2); - tiling_flags |= AMDGPU_TILING_SET(BANK_HEIGHT, 1); - tiling_flags |= AMDGPU_TILING_SET(MACRO_TILE_ASPECT, 3); - tiling_flags |= AMDGPU_TILING_SET(TILE_SPLIT, 4); - tiling_flags |= AMDGPU_TILING_SET(NUM_BANKS, 2); - tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 7); - - amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(&tiling_info, tiling_flags); - - KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion8); - KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.array_mode, (int)DC_ARRAY_2D_TILED_THIN1); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.bank_width, 2U); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.bank_height, 1U); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_aspect, 3U); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_split, 4U); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.num_banks, 2U); - KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.tile_mode, - (int)DC_ADDR_SURF_MICRO_TILING_DISPLAY); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.pipe_config, 7U); -} - -/** - * dm_test_fill_gfx8_tiling_info_1d_tiled() - Verify GFX8 1D tiled flag parsing. - * @test: KUnit test context. - * - * Verify if 1D tiled GFX8 flags populate array mode and pipe config. - */ -static void dm_test_fill_gfx8_tiling_info_1d_tiled(struct kunit *test) -{ - struct dc_tiling_info tiling_info = {0}; - uint64_t tiling_flags = 0; - - tiling_flags |= AMDGPU_TILING_SET(ARRAY_MODE, DC_ARRAY_1D_TILED_THIN1); - tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 5); - - amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(&tiling_info, tiling_flags); - - KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.array_mode, (int)DC_ARRAY_1D_TILED_THIN1); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.pipe_config, 5U); -} - -/** - * dm_test_fill_gfx8_tiling_info_other_mode() - Verify non-1D/non-2D mode handling. - * @test: KUnit test context. - * - * Verify if unsupported array mode keeps preset fields and updates pipe config. - */ -static void dm_test_fill_gfx8_tiling_info_other_mode(struct kunit *test) -{ - struct dc_tiling_info tiling_info = {0}; - uint64_t tiling_flags = 0; - - tiling_info.gfxversion = 0x7f; - tiling_info.gfx8.array_mode = 0x7f; - tiling_info.gfx8.tile_mode = 0x7f; - tiling_info.gfx8.num_banks = 0x7f; - - tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 6); - - amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(&tiling_info, tiling_flags); - - KUNIT_EXPECT_EQ(test, tiling_info.gfxversion, 0x7f); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.array_mode, 0x7f); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_mode, 0x7f); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.num_banks, 0x7f); - KUNIT_EXPECT_EQ(test, tiling_info.gfx8.pipe_config, 6U); -} - /** * dm_test_fill_gfx9_tiling_info_from_device_pre_10_3() - Verify GFX9 field copy before 10.3. * @test: KUnit test context. @@ -1399,7 +1263,7 @@ static int dm_test_plane_attrs(struct amdgpu_device *adev, struct dc_plane_address *address) { return amdgpu_dm_plane_fill_plane_buffer_attributes(adev, afb, format, - ROTATION_ANGLE_0, 0, tiling_info, plane_size, dcc, address, + ROTATION_ANGLE_0, tiling_info, plane_size, dcc, address, false); } @@ -3195,7 +3059,6 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_get_min_max_dc_plane_scaling), KUNIT_CASE(dm_test_get_min_max_dc_plane_scaling_fp16), /* amdgpu_dm_plane_fill_plane_buffer_attributes() */ - KUNIT_CASE(dm_test_fill_plane_buffer_attributes_gfx8), KUNIT_CASE(dm_test_fill_plane_buffer_attributes_video), KUNIT_CASE(dm_test_fill_plane_buffer_attributes_gfx12), /* amdgpu_dm_plane_get_cursor_position() */ @@ -3245,10 +3108,6 @@ static struct kunit_case amdgpu_dm_plane_test_cases[] = { KUNIT_CASE(dm_test_add_modifier_appends_value), KUNIT_CASE(dm_test_add_modifier_grows_capacity), KUNIT_CASE(dm_test_add_modifier_noop_when_mods_null), - /* amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags() */ - KUNIT_CASE(dm_test_fill_gfx8_tiling_info_2d_tiled), - KUNIT_CASE(dm_test_fill_gfx8_tiling_info_1d_tiled), - KUNIT_CASE(dm_test_fill_gfx8_tiling_info_other_mode), /* amdgpu_dm_plane_fill_gfx9_tiling_info_from_device() */ KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_pre_10_3), KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_10_3_plus), -- cgit v1.2.3 From da8609eef18b0a3490d0e1fa9440659fadc8194d Mon Sep 17 00:00:00 2001 From: Alan Swanson Date: Mon, 27 Jul 2026 17:01:26 +0100 Subject: drm/amd/display: Silence link_dpms I2C retimer failures Commit a4f01bf729b2 ("drm/amd/display: Refactor and fix link_dpms I2C") had also changed the "Set retimer failed" messages from DC_LOG_DEBUG() to DC_LOG_ERROR(). This unfortunately can create log spam. Change those back to DC_LOG_DEBUG() only. Fixes: a4f01bf729b2 ("drm/amd/display: Refactor and fix link_dpms I2C") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5520 Signed-off-by: Alan Swanson Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/link/link_dpms.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c index 949c669eb259..48b086d15ab0 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c @@ -384,7 +384,7 @@ static bool write_i2c_retimer_vga( for (size_t i = 0; i < ARRAY_SIZE(vga_data); i++) { if (!write_i2c_retimer_offset_value(link, address, vga_data[i][0], vga_data[i][1])) { - DC_LOG_ERROR("Set retimer failed, vga index: %zu\n", i); + DC_LOG_DEBUG("Set retimer failed, vga index: %zu\n", i); return false; } } @@ -405,7 +405,7 @@ static bool write_i2c_retimer_byte( return true; if (!write_i2c_retimer_offset_value(link, address, index, value)) { - DC_LOG_ERROR("Set retimer failed, 3g index: 0x%x, value: 0x%x\n", index, value); + DC_LOG_DEBUG("Set retimer failed, 3g index: 0x%x, value: 0x%x\n", index, value); return false; } @@ -421,14 +421,14 @@ static bool write_i2c_retimer_byte( if (!link_query_ddc_data( link->ddc, address, &offset, 1, &value, 1 )) { - DC_LOG_ERROR("Set retimer failed, link_query_ddc_data\n"); + DC_LOG_DEBUG("Set retimer failed, link_query_ddc_data\n"); return false; } } value |= apply_rx_tx_change; if (!write_i2c_retimer_offset_value(link, address, offset, value)) { - DC_LOG_ERROR("Set retimer failed, 3g offset: 0x%x, value: 0x%x\n", offset, value); + DC_LOG_DEBUG("Set retimer failed, 3g offset: 0x%x, value: 0x%x\n", offset, value); return false; } } @@ -449,7 +449,7 @@ static bool write_i2c_retimer_setting( uint8_t value = settings->reg_settings[i].i2c_reg_val; if (!write_i2c_retimer_byte(link, address, index, value)) { - DC_LOG_ERROR("Set retimer failed, index: %zu\n", i); + DC_LOG_DEBUG("Set retimer failed, index: %zu\n", i); return false; } } @@ -460,7 +460,7 @@ static bool write_i2c_retimer_setting( uint8_t value = settings->reg_settings_6g[i].i2c_reg_val; if (!write_i2c_retimer_byte(link, address, index, value)) { - DC_LOG_ERROR("Set retimer failed, 6g index: %zu\n", i); + DC_LOG_DEBUG("Set retimer failed, 6g index: %zu\n", i); return false; } } @@ -492,7 +492,7 @@ static bool write_i2c_default_retimer_setting( for (size_t i = 0; i < ARRAY_SIZE(data); i++) { if (!write_i2c_retimer_offset_value(link, address, data[i][0], data[i][1])) { - DC_LOG_ERROR("Set default retimer failed, index: %zu\n", i); + DC_LOG_DEBUG("Set default retimer failed, index: %zu\n", i); return false; } } @@ -524,7 +524,7 @@ static bool write_i2c_redriver_setting( ); if (!success) - DC_LOG_ERROR("Set redriver failed"); + DC_LOG_DEBUG("Set redriver failed"); return success; } -- cgit v1.2.3 From a5be7ad8f5f0e067613e9197638f216f46252946 Mon Sep 17 00:00:00 2001 From: Andre Eikmeyer Date: Tue, 21 Jul 2026 16:01:31 +0200 Subject: drm/amdgpu: reset VI ASIC on MacBookPro15,1 After S3, reloading amdgpu on MacBookPro15,1 systems with a Radeon Pro 555X or 560X fails while loading the SMU firmware. The existing SMC register check does not request a reset because the registers do not reliably reflect the stale SMU state on these machines. Frederick Morlock found that forcing a VI ASIC reset allows the driver to initialize again. This patch limits his workaround to the exact PCI device, Apple subsystem device and revision combinations used by these two GPUs, leaving other VI hardware unchanged. Suggested-by: Frederick Morlock Signed-off-by: Andre Eikmeyer Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vi.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/vi.c b/drivers/gpu/drm/amd/amdgpu/vi.c index 514715793f78..352c27103305 100644 --- a/drivers/gpu/drm/amd/amdgpu/vi.c +++ b/drivers/gpu/drm/amd/amdgpu/vi.c @@ -1387,10 +1387,30 @@ static uint64_t vi_get_pcie_replay_count(struct amdgpu_device *adev) return (nak_r + nak_g); } +struct vi_reset_quirk { + u16 device; + u16 subsystem_vendor; + u16 subsystem_device; + u8 revision; +}; + +static const struct vi_reset_quirk vi_reset_quirks[] = { + { 0x67ef, PCI_VENDOR_ID_APPLE, 0x0190, 0xe3 }, /* Radeon Pro 555X */ + { 0x67ef, PCI_VENDOR_ID_APPLE, 0x018f, 0xc2 }, /* Radeon Pro 560X */ +}; + static bool vi_need_reset_on_init(struct amdgpu_device *adev) { + unsigned int i; u32 clock_cntl, pc; + for (i = 0; i < ARRAY_SIZE(vi_reset_quirks); i++) + if (adev->pdev->device == vi_reset_quirks[i].device && + adev->pdev->subsystem_vendor == vi_reset_quirks[i].subsystem_vendor && + adev->pdev->subsystem_device == vi_reset_quirks[i].subsystem_device && + adev->pdev->revision == vi_reset_quirks[i].revision) + return true; + if (adev->flags & AMD_IS_APU) return false; -- cgit v1.2.3 From e3fcdd6194264dda91978a1c97984f83c4e86dfc Mon Sep 17 00:00:00 2001 From: William Palacek Date: Mon, 20 Jul 2026 11:47:36 -0400 Subject: drm/amdkfd: allow CWSR grace period of 0 on supported firmware The debugger sets the CWSR grace period via AMDKFD_IOC_DBG_TRAP and may request a value of 0. An earlier change clamped 0 to 1 unconditionally because most firmware revisions locked up when given an infinite (0) grace period. Firmware has since been fixed on most ASICs, so gate the clamp on ASIC type and MEC firmware version and allow 0 where the running firmware is known to handle it. Navi3x and MI350 support 0 in every firmware revision and need no version check. MI100 never received the firmware fix and is kept clamped. Any unlisted or future ASIC defaults to the safe (clamped) behaviour. Signed-off-by: William Palacek Reviewed-by: Kent Russell Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_packet_manager_v9.c | 70 ++++++++++++++++++++-- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager_v9.c b/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager_v9.c index 3d2375817c3e..54868b81086e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_packet_manager_v9.c @@ -309,6 +309,67 @@ static inline void pm_build_dequeue_wait_counts_packet_info(struct packet_manage reg_data); } +/* pm_grace_period_0_supported - whether firmware tolerates a CWSR grace + * period of 0 on this ASIC. + * + * The debugger may request a grace period of 0 via AMDKFD_IOC_DBG_TRAP. + * Most firmware revisions locked up on an infinite (0) grace period, so + * an earlier change clamped 0 to 1 for all ASICs. Firmware has since been + * fixed on most ASICs; return true only where the running MEC firmware is + * known to handle 0. The +32768 offset on IP_VERSION(9, 0, 1) accounts for + * the SR-IOV firmware version encoding. + * + * Navi3x (gfx11) and MI350 (IP_VERSION(9, 5, 0)) support a grace period of + * 0 in every firmware revision and need no version check. MI100 + * (IP_VERSION(9, 4, 1)) never received the fix and is intentionally kept + * clamped. Any unlisted or future ASIC defaults to the safe behaviour + * (clamp). + */ +static bool pm_grace_period_0_supported(struct packet_manager *pm) +{ + struct kfd_node *dev = pm->dqm->dev; + uint32_t mec_fw_version = dev->kfd->mec_fw_version; + + /* Navi3x (gfx11) always supports a grace period of 0. */ + if (KFD_GC_VERSION(dev) >= IP_VERSION(11, 0, 0) && + KFD_GC_VERSION(dev) < IP_VERSION(12, 0, 0)) + return true; + + switch (KFD_GC_VERSION(dev)) { + case IP_VERSION(9, 0, 1): + return mec_fw_version >= 461 + 32768; + case IP_VERSION(9, 1, 0): + case IP_VERSION(9, 2, 1): + case IP_VERSION(9, 2, 2): + case IP_VERSION(9, 3, 0): + case IP_VERSION(9, 4, 0): + return mec_fw_version >= 461; + /* MI100/Arcturus never received the firmware fix; keep clamped. */ + case IP_VERSION(9, 4, 1): + return false; + case IP_VERSION(9, 4, 2): + return mec_fw_version >= 63; + case IP_VERSION(9, 4, 3): + case IP_VERSION(9, 4, 4): + return mec_fw_version >= 96; + /* MI350 supports a grace period of 0 in every firmware revision. */ + case IP_VERSION(9, 5, 0): + return true; + case IP_VERSION(10, 1, 10): + case IP_VERSION(10, 1, 2): + case IP_VERSION(10, 1, 1): + return mec_fw_version >= 146; + case IP_VERSION(10, 3, 0): + case IP_VERSION(10, 3, 2): + case IP_VERSION(10, 3, 1): + case IP_VERSION(10, 3, 4): + case IP_VERSION(10, 3, 5): + return mec_fw_version >= 93; + default: + return false; + } +} + /* pm_config_dequeue_wait_counts_v9: Builds WRITE_DATA packet with * register/value for configuring dequeue wait counts * @@ -357,11 +418,12 @@ static int pm_config_dequeue_wait_counts_v9(struct packet_manager *pm, break; case KFD_DEQUEUE_WAIT_SET_SCH_WAVE: - /* The CP cannot handle value 0 and it will result in - * an infinite grace period being set so set to 1 to prevent this. Also - * avoid debugger API breakage as it sets 0 and expects a low value. + /* A grace period of 0 requests an infinite CWSR grace period. + * Older firmware locks up on this, so clamp to 1 unless the ASIC + * firmware is known to handle 0. Also avoid debugger API breakage + * as it sets 0 and expects a low value. */ - if (!value) + if (!value && !pm_grace_period_0_supported(pm)) value = 1; pm_build_dequeue_wait_counts_packet_info(pm, value, 0, ®_offset, ®_data); break; -- cgit v1.2.3 From bfb5b57bebbaad1ea89945ed0a51613c7fe87456 Mon Sep 17 00:00:00 2001 From: "Uwe Kleine-König (The Capable Hub)" Date: Mon, 27 Jul 2026 12:40:04 -0400 Subject: drm/amdgpu: Consistently define pci_device_ids using named initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... and PCI device helpers. The struct pci_device_id array of supported device was initialized by list expressions. This isn't easily readable if you're not into PCI. Use PCI_DEVICE* helper macros and named initializers which is more explicit and thus easier to parse. Also skip explicit assignments of 0 (which the compiler then takes care of). The secret plan is to make struct pci_device_id::driver_data an anonymous union (similar to https://lore.kernel.org/all/cover.1776579304.git.u.kleine-koenig@baylibre.com/) and that requires named initializers. But it's also a nice cleanup on its own. This change doesn't introduce changes to the compiled pci_device_id array. Tested on x86 and arm64. Signed-off-by: Uwe Kleine-König (The Capable Hub) Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 626 ++++++++++++++++---------------- 1 file changed, 313 insertions(+), 313 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 5362705143bc..6c8ba5bd45ca 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -1838,357 +1838,357 @@ static const u16 amdgpu_unsupported_pciidlist[] = { }; static const struct pci_device_id pciidlist[] = { - {0x1002, 0x6780, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6784, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6788, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x678A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6790, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6791, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6792, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6798, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6799, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x679A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x679B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x679E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x679F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI}, - {0x1002, 0x6800, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|AMD_IS_MOBILITY}, - {0x1002, 0x6801, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|AMD_IS_MOBILITY}, - {0x1002, 0x6802, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|AMD_IS_MOBILITY}, - {0x1002, 0x6806, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6808, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6809, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6810, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6811, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6816, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6817, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6818, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6819, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN}, - {0x1002, 0x6600, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6601, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6602, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6603, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6604, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6605, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6606, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6607, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6608, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND}, - {0x1002, 0x6610, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND}, - {0x1002, 0x6611, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND}, - {0x1002, 0x6613, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND}, - {0x1002, 0x6617, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6620, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6621, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6623, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|AMD_IS_MOBILITY}, - {0x1002, 0x6631, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND}, - {0x1002, 0x6820, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6821, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6822, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6823, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6824, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6825, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6826, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6827, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6828, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x6829, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x682A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x682B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x682C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x682D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x682F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6830, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6831, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|AMD_IS_MOBILITY}, - {0x1002, 0x6835, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x6837, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x6838, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x6839, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x683B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x683D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x683F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE}, - {0x1002, 0x6660, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, - {0x1002, 0x6663, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, - {0x1002, 0x6664, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, - {0x1002, 0x6665, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, - {0x1002, 0x6667, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, - {0x1002, 0x666F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|AMD_IS_MOBILITY}, + { PCI_DEVICE(0x1002, 0x6780), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6784), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6788), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x678A), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6790), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6791), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6792), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6798), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6799), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x679A), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x679B), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x679E), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x679F), .driver_data = CHIP_TAHITI }, + { PCI_DEVICE(0x1002, 0x6800), .driver_data = CHIP_PITCAIRN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6801), .driver_data = CHIP_PITCAIRN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6802), .driver_data = CHIP_PITCAIRN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6806), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6808), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6809), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6810), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6811), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6816), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6817), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6818), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6819), .driver_data = CHIP_PITCAIRN }, + { PCI_DEVICE(0x1002, 0x6600), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6601), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6602), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6603), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6604), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6605), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6606), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6607), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6608), .driver_data = CHIP_OLAND }, + { PCI_DEVICE(0x1002, 0x6610), .driver_data = CHIP_OLAND }, + { PCI_DEVICE(0x1002, 0x6611), .driver_data = CHIP_OLAND }, + { PCI_DEVICE(0x1002, 0x6613), .driver_data = CHIP_OLAND }, + { PCI_DEVICE(0x1002, 0x6617), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6620), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6621), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6623), .driver_data = CHIP_OLAND|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6631), .driver_data = CHIP_OLAND }, + { PCI_DEVICE(0x1002, 0x6820), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6821), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6822), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6823), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6824), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6825), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6826), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6827), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6828), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x6829), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x682A), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x682B), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x682C), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x682D), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x682F), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6830), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6831), .driver_data = CHIP_VERDE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6835), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x6837), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x6838), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x6839), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x683B), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x683D), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x683F), .driver_data = CHIP_VERDE }, + { PCI_DEVICE(0x1002, 0x6660), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6663), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6664), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6665), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6667), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x666F), .driver_data = CHIP_HAINAN|AMD_IS_MOBILITY }, /* Kaveri */ - {0x1002, 0x1304, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x1305, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1306, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x1307, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1309, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x130F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1310, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1311, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1312, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1313, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1315, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1316, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x1317, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x1318, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x131B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x131C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, - {0x1002, 0x131D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x1304), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1305), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1306), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1307), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1309), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130A), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130B), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130C), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130D), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130E), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x130F), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1310), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1311), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1312), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1313), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1315), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1316), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1317), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1318), .driver_data = CHIP_KAVERI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x131B), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x131C), .driver_data = CHIP_KAVERI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x131D), .driver_data = CHIP_KAVERI|AMD_IS_APU }, /* Bonaire */ - {0x1002, 0x6640, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|AMD_IS_MOBILITY}, - {0x1002, 0x6641, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|AMD_IS_MOBILITY}, - {0x1002, 0x6646, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|AMD_IS_MOBILITY}, - {0x1002, 0x6647, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|AMD_IS_MOBILITY}, - {0x1002, 0x6649, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x664D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x6650, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x6651, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x6658, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x665c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x665d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, - {0x1002, 0x665f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE}, + { PCI_DEVICE(0x1002, 0x6640), .driver_data = CHIP_BONAIRE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6641), .driver_data = CHIP_BONAIRE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6646), .driver_data = CHIP_BONAIRE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6647), .driver_data = CHIP_BONAIRE|AMD_IS_MOBILITY }, + { PCI_DEVICE(0x1002, 0x6649), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x664D), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x6650), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x6651), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x6658), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x665c), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x665d), .driver_data = CHIP_BONAIRE }, + { PCI_DEVICE(0x1002, 0x665f), .driver_data = CHIP_BONAIRE }, /* Hawaii */ - {0x1002, 0x67A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67A1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67A2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67A8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67A9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67AA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67B0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67B1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67B8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67B9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67BA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, - {0x1002, 0x67BE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII}, + { PCI_DEVICE(0x1002, 0x67A0), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67A1), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67A2), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67A8), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67A9), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67AA), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67B0), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67B1), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67B8), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67B9), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67BA), .driver_data = CHIP_HAWAII }, + { PCI_DEVICE(0x1002, 0x67BE), .driver_data = CHIP_HAWAII }, /* Kabini */ - {0x1002, 0x9830, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9831, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x9832, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9833, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x9834, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9835, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x9836, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9837, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x9838, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9839, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x983a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x983b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x983c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x983d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x983e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, - {0x1002, 0x983f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x9830), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9831), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9832), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9833), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9834), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9835), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9836), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9837), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9838), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9839), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983a), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983b), .driver_data = CHIP_KABINI|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983c), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983d), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983e), .driver_data = CHIP_KABINI|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x983f), .driver_data = CHIP_KABINI|AMD_IS_APU }, /* mullins */ - {0x1002, 0x9850, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9851, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9852, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9853, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9854, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9855, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9856, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9857, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9858, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x9859, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, - {0x1002, 0x985F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x9850), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9851), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9852), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9853), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9854), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9855), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9856), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9857), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9858), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9859), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985A), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985B), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985C), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985D), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985E), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x985F), .driver_data = CHIP_MULLINS|AMD_IS_MOBILITY|AMD_IS_APU }, /* topaz */ - {0x1002, 0x6900, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TOPAZ}, - {0x1002, 0x6901, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TOPAZ}, - {0x1002, 0x6902, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TOPAZ}, - {0x1002, 0x6903, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TOPAZ}, - {0x1002, 0x6907, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TOPAZ}, + { PCI_DEVICE(0x1002, 0x6900), .driver_data = CHIP_TOPAZ }, + { PCI_DEVICE(0x1002, 0x6901), .driver_data = CHIP_TOPAZ }, + { PCI_DEVICE(0x1002, 0x6902), .driver_data = CHIP_TOPAZ }, + { PCI_DEVICE(0x1002, 0x6903), .driver_data = CHIP_TOPAZ }, + { PCI_DEVICE(0x1002, 0x6907), .driver_data = CHIP_TOPAZ }, /* tonga */ - {0x1002, 0x6920, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6921, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6928, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6929, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x692B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x692F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6930, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6938, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x6939, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, - {0x1002, 0x693B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TONGA}, + { PCI_DEVICE(0x1002, 0x6920), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6921), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6928), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6929), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x692B), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x692F), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6930), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6938), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x6939), .driver_data = CHIP_TONGA }, + { PCI_DEVICE(0x1002, 0x693B), .driver_data = CHIP_TONGA }, /* fiji */ - {0x1002, 0x7300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_FIJI}, - {0x1002, 0x730F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_FIJI}, + { PCI_DEVICE(0x1002, 0x7300), .driver_data = CHIP_FIJI }, + { PCI_DEVICE(0x1002, 0x730F), .driver_data = CHIP_FIJI }, /* carrizo */ - {0x1002, 0x9870, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CARRIZO|AMD_IS_APU}, - {0x1002, 0x9874, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CARRIZO|AMD_IS_APU}, - {0x1002, 0x9875, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CARRIZO|AMD_IS_APU}, - {0x1002, 0x9876, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CARRIZO|AMD_IS_APU}, - {0x1002, 0x9877, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CARRIZO|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x9870), .driver_data = CHIP_CARRIZO|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9874), .driver_data = CHIP_CARRIZO|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9875), .driver_data = CHIP_CARRIZO|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9876), .driver_data = CHIP_CARRIZO|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x9877), .driver_data = CHIP_CARRIZO|AMD_IS_APU }, /* stoney */ - {0x1002, 0x98E4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_STONEY|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x98E4), .driver_data = CHIP_STONEY|AMD_IS_APU }, /* Polaris11 */ - {0x1002, 0x67E0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67EB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67EF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67FF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, + { PCI_DEVICE(0x1002, 0x67E0), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67E3), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67E8), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67EB), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67EF), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67FF), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67E1), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67E7), .driver_data = CHIP_POLARIS11 }, + { PCI_DEVICE(0x1002, 0x67E9), .driver_data = CHIP_POLARIS11 }, /* Polaris10 */ - {0x1002, 0x67C0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67D0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67D4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67DF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67C9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67CA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67CC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x67CF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, - {0x1002, 0x6FDF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + { PCI_DEVICE(0x1002, 0x67C0), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C1), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C2), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C4), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C7), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67D0), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67D4), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67DF), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C8), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67C9), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67CA), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67CC), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x67CF), .driver_data = CHIP_POLARIS10 }, + { PCI_DEVICE(0x1002, 0x6FDF), .driver_data = CHIP_POLARIS10 }, /* Polaris12 */ - {0x1002, 0x6980, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6981, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6985, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6986, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6987, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x698F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6995, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x6997, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, - {0x1002, 0x699F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS12}, + { PCI_DEVICE(0x1002, 0x6980), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6981), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6985), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6986), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6987), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x698F), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6995), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x6997), .driver_data = CHIP_POLARIS12 }, + { PCI_DEVICE(0x1002, 0x699F), .driver_data = CHIP_POLARIS12 }, /* VEGAM */ - {0x1002, 0x694C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGAM}, - {0x1002, 0x694E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGAM}, - {0x1002, 0x694F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGAM}, + { PCI_DEVICE(0x1002, 0x694C), .driver_data = CHIP_VEGAM }, + { PCI_DEVICE(0x1002, 0x694E), .driver_data = CHIP_VEGAM }, + { PCI_DEVICE(0x1002, 0x694F), .driver_data = CHIP_VEGAM }, /* Vega 10 */ - {0x1002, 0x6860, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6861, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6862, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6863, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6864, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6867, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6868, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x6869, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x686f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, - {0x1002, 0x687f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA10}, + { PCI_DEVICE(0x1002, 0x6860), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6861), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6862), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6863), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6864), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6867), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6868), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x6869), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686a), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686b), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686c), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686d), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686e), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x686f), .driver_data = CHIP_VEGA10 }, + { PCI_DEVICE(0x1002, 0x687f), .driver_data = CHIP_VEGA10 }, /* Vega 12 */ - {0x1002, 0x69A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA12}, - {0x1002, 0x69A1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA12}, - {0x1002, 0x69A2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA12}, - {0x1002, 0x69A3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA12}, - {0x1002, 0x69AF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA12}, + { PCI_DEVICE(0x1002, 0x69A0), .driver_data = CHIP_VEGA12 }, + { PCI_DEVICE(0x1002, 0x69A1), .driver_data = CHIP_VEGA12 }, + { PCI_DEVICE(0x1002, 0x69A2), .driver_data = CHIP_VEGA12 }, + { PCI_DEVICE(0x1002, 0x69A3), .driver_data = CHIP_VEGA12 }, + { PCI_DEVICE(0x1002, 0x69AF), .driver_data = CHIP_VEGA12 }, /* Vega 20 */ - {0x1002, 0x66A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66A1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66A2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66A3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66A4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66A7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, - {0x1002, 0x66AF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VEGA20}, + { PCI_DEVICE(0x1002, 0x66A0), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66A1), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66A2), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66A3), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66A4), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66A7), .driver_data = CHIP_VEGA20 }, + { PCI_DEVICE(0x1002, 0x66AF), .driver_data = CHIP_VEGA20 }, /* Raven */ - {0x1002, 0x15dd, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RAVEN|AMD_IS_APU}, - {0x1002, 0x15d8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RAVEN|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x15dd), .driver_data = CHIP_RAVEN|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x15d8), .driver_data = CHIP_RAVEN|AMD_IS_APU }, /* Arcturus */ - {0x1002, 0x738C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARCTURUS}, - {0x1002, 0x7388, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARCTURUS}, - {0x1002, 0x738E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARCTURUS}, - {0x1002, 0x7390, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARCTURUS}, + { PCI_DEVICE(0x1002, 0x738C), .driver_data = CHIP_ARCTURUS }, + { PCI_DEVICE(0x1002, 0x7388), .driver_data = CHIP_ARCTURUS }, + { PCI_DEVICE(0x1002, 0x738E), .driver_data = CHIP_ARCTURUS }, + { PCI_DEVICE(0x1002, 0x7390), .driver_data = CHIP_ARCTURUS }, /* Navi10 */ - {0x1002, 0x7310, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x7312, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x7318, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x7319, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x731A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x731B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x731E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, - {0x1002, 0x731F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI10}, + { PCI_DEVICE(0x1002, 0x7310), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x7312), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x7318), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x7319), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x731A), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x731B), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x731E), .driver_data = CHIP_NAVI10 }, + { PCI_DEVICE(0x1002, 0x731F), .driver_data = CHIP_NAVI10 }, /* Navi14 */ - {0x1002, 0x7340, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI14}, - {0x1002, 0x7341, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI14}, - {0x1002, 0x7347, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI14}, - {0x1002, 0x734F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI14}, + { PCI_DEVICE(0x1002, 0x7340), .driver_data = CHIP_NAVI14 }, + { PCI_DEVICE(0x1002, 0x7341), .driver_data = CHIP_NAVI14 }, + { PCI_DEVICE(0x1002, 0x7347), .driver_data = CHIP_NAVI14 }, + { PCI_DEVICE(0x1002, 0x734F), .driver_data = CHIP_NAVI14 }, /* Renoir */ - {0x1002, 0x15E7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RENOIR|AMD_IS_APU}, - {0x1002, 0x1636, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RENOIR|AMD_IS_APU}, - {0x1002, 0x1638, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RENOIR|AMD_IS_APU}, - {0x1002, 0x164C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RENOIR|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x15E7), .driver_data = CHIP_RENOIR|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1636), .driver_data = CHIP_RENOIR|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1638), .driver_data = CHIP_RENOIR|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x164C), .driver_data = CHIP_RENOIR|AMD_IS_APU }, /* Navi12 */ - {0x1002, 0x7360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI12}, - {0x1002, 0x7362, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVI12}, + { PCI_DEVICE(0x1002, 0x7360), .driver_data = CHIP_NAVI12 }, + { PCI_DEVICE(0x1002, 0x7362), .driver_data = CHIP_NAVI12 }, /* Sienna_Cichlid */ - {0x1002, 0x73A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A5, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73A9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73AB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73AC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73AD, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73AE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73AF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, - {0x1002, 0x73BF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SIENNA_CICHLID}, + { PCI_DEVICE(0x1002, 0x73A0), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A1), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A2), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A3), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A5), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A8), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73A9), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73AB), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73AC), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73AD), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73AE), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73AF), .driver_data = CHIP_SIENNA_CICHLID }, + { PCI_DEVICE(0x1002, 0x73BF), .driver_data = CHIP_SIENNA_CICHLID }, /* Yellow Carp */ - {0x1002, 0x164D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_YELLOW_CARP|AMD_IS_APU}, - {0x1002, 0x1681, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_YELLOW_CARP|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x164D), .driver_data = CHIP_YELLOW_CARP|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x1681), .driver_data = CHIP_YELLOW_CARP|AMD_IS_APU }, /* Navy_Flounder */ - {0x1002, 0x73C0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73C1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73C3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DD, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, - {0x1002, 0x73DF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_NAVY_FLOUNDER}, + { PCI_DEVICE(0x1002, 0x73C0), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73C1), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73C3), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DA), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DB), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DC), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DD), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DE), .driver_data = CHIP_NAVY_FLOUNDER }, + { PCI_DEVICE(0x1002, 0x73DF), .driver_data = CHIP_NAVY_FLOUNDER }, /* DIMGREY_CAVEFISH */ - {0x1002, 0x73E0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73E1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73E2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73E3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73E8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73E9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73EA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73EB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73EC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73ED, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73EF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, - {0x1002, 0x73FF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_DIMGREY_CAVEFISH}, + { PCI_DEVICE(0x1002, 0x73E0), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73E1), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73E2), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73E3), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73E8), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73E9), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73EA), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73EB), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73EC), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73ED), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73EF), .driver_data = CHIP_DIMGREY_CAVEFISH }, + { PCI_DEVICE(0x1002, 0x73FF), .driver_data = CHIP_DIMGREY_CAVEFISH }, /* Aldebaran */ - {0x1002, 0x7408, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ALDEBARAN}, - {0x1002, 0x740C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ALDEBARAN}, - {0x1002, 0x740F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ALDEBARAN}, - {0x1002, 0x7410, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ALDEBARAN}, + { PCI_DEVICE(0x1002, 0x7408), .driver_data = CHIP_ALDEBARAN }, + { PCI_DEVICE(0x1002, 0x740C), .driver_data = CHIP_ALDEBARAN }, + { PCI_DEVICE(0x1002, 0x740F), .driver_data = CHIP_ALDEBARAN }, + { PCI_DEVICE(0x1002, 0x7410), .driver_data = CHIP_ALDEBARAN }, /* CYAN_SKILLFISH */ - {0x1002, 0x13DB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x13F9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x13FA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x13FB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x13FC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x13FE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, - {0x1002, 0x143F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYAN_SKILLFISH|AMD_IS_APU}, + { PCI_DEVICE(0x1002, 0x13DB), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x13F9), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x13FA), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x13FB), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x13FC), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x13FE), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, + { PCI_DEVICE(0x1002, 0x143F), .driver_data = CHIP_CYAN_SKILLFISH|AMD_IS_APU }, /* BEIGE_GOBY */ - {0x1002, 0x7420, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, - {0x1002, 0x7421, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, - {0x1002, 0x7422, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, - {0x1002, 0x7423, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, - {0x1002, 0x7424, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, - {0x1002, 0x743F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BEIGE_GOBY}, + { PCI_DEVICE(0x1002, 0x7420), .driver_data = CHIP_BEIGE_GOBY }, + { PCI_DEVICE(0x1002, 0x7421), .driver_data = CHIP_BEIGE_GOBY }, + { PCI_DEVICE(0x1002, 0x7422), .driver_data = CHIP_BEIGE_GOBY }, + { PCI_DEVICE(0x1002, 0x7423), .driver_data = CHIP_BEIGE_GOBY }, + { PCI_DEVICE(0x1002, 0x7424), .driver_data = CHIP_BEIGE_GOBY }, + { PCI_DEVICE(0x1002, 0x743F), .driver_data = CHIP_BEIGE_GOBY }, { PCI_DEVICE(0x1002, PCI_ANY_ID), .class = PCI_CLASS_DISPLAY_VGA << 8, @@ -2205,7 +2205,7 @@ static const struct pci_device_id pciidlist[] = { .class_mask = 0xffffff, .driver_data = CHIP_IP_DISCOVERY }, - {0, 0, 0} + { } }; MODULE_DEVICE_TABLE(pci, pciidlist); -- cgit v1.2.3 From d3b00fd1633a2846581aed582a48ce2498509f65 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Sat, 11 Jul 2026 13:50:00 +0200 Subject: drm/amdgpu/ttm: Use more optimal copy packet sizes for copy and fill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently when amdgpu copies or fills a buffer, it uses the maximum byte count supported by the copy engine (SDMA). This is problematic when the maximum byte count is not aligned to 256 bytes because it then can't use all memory channels optimally and can cause the SDMA to operate in its slower byte mode (as opposed to the faster dword mode). For example, when copying a 10 MiB buffer on SDMA v2.4, we get 5 packets copying 2097151 bytes and 1 packet copying the remaining 5 bytes. All 6 packets are misaligned and operate in byte mode. For this example, the optimal solution would be to have 5 packets each copying 2096896 bytes and 1 last packet to copy the remaining 1280 bytes, in which case all 6 packets are aligned to 256 bytes and operate in dword mode. Let's use the following scheme from now on: When byte count is dword-aligned and fits a single packet, just emit a single packet. Otherwise, align the copy packet size down to 256 bytes for optimal use of memory channels and to ensure the HW can use the dword mode. This assumes that the starting addresses of BOs are always dword aligned, which should be the case for every copy operation in the kernel, because the kernel always copies pages. Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 03c1e5e3580c..5fe29e4972d8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2461,6 +2461,27 @@ static int amdgpu_ttm_prepare_job(struct amdgpu_device *adev, DMA_RESV_USAGE_BOOKKEEP); } +static int amdgpu_calc_bytes_per_packet(u32 max_bytes_per_packet, + u32 byte_count) +{ + /* Byte count is dword-aligned and fits a single packet */ + if (!(byte_count & 0x3) && byte_count <= max_bytes_per_packet) + return max_bytes_per_packet; + + /* + * Align down maximum byte count to 256 bytes so that + * the copy optimally uses all memory channels and + * also to ensure that SDMA can use its dword mode, which + * is faster. + * + * This assumes that the starting addresses of BOs are always + * dword aligned, which should be the case for every copy + * operation in the kernel, because the kernel always copies + * pages. + */ + return ALIGN_DOWN(max_bytes_per_packet, SZ_256); +} + int amdgpu_copy_buffer(struct amdgpu_device *adev, struct amdgpu_ttm_buffer_entity *entity, uint64_t src_offset, @@ -2484,7 +2505,8 @@ int amdgpu_copy_buffer(struct amdgpu_device *adev, return -EINVAL; } - max_bytes = adev->mman.buffer_funcs->copy_max_bytes; + max_bytes = amdgpu_calc_bytes_per_packet(adev->mman.buffer_funcs->copy_max_bytes, + byte_count); num_loops = DIV_ROUND_UP(byte_count, max_bytes); num_dw = ALIGN(num_loops * adev->mman.buffer_funcs->copy_num_dw, 8); r = amdgpu_ttm_prepare_job(adev, entity, num_dw, @@ -2528,7 +2550,8 @@ static int amdgpu_ttm_fill_mem(struct amdgpu_device *adev, unsigned int i; int r; - max_bytes = adev->mman.buffer_funcs->fill_max_bytes; + max_bytes = amdgpu_calc_bytes_per_packet(adev->mman.buffer_funcs->fill_max_bytes, + byte_count); num_loops = DIV_ROUND_UP_ULL(byte_count, max_bytes); num_dw = ALIGN(num_loops * adev->mman.buffer_funcs->fill_num_dw, 8); r = amdgpu_ttm_prepare_job(adev, entity, num_dw, resv, -- cgit v1.2.3 From 18bcc49d879c8223d80da9a11a8c00e38dd05811 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Thu, 23 Jul 2026 10:34:33 +0800 Subject: drm/amd/display: Fix DML file discovery for out-of-tree builds By anchoring the search to the DML top-level Makefile directory, FPU CFLAGS are now correctly applied to all relevant source files. Signed-off-by: Prike Liang Reviewed-by: Leo Li Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dml2_0/Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile index 44e00c2b7ac7..e9ce7813b0f5 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile @@ -55,8 +55,11 @@ subdir-ccflags-y += -I$(FULL_AMD_DISPLAY_PATH)/dc/dml2_0/dml21/ # Add FPU flags to all dml2 files by default, remove NO_FPU flags. # FPU flags step 1: Find all .c files in dal/dc/dml2_0 and it's subfolders -DML2_ABS_PATH := $(FULL_AMD_DISPLAY_PATH)/dc/dml2_0 -DML2_C_FILES := $(shell find $(DML2_ABS_PATH) -name '*.c' -type f) + +THIS_MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +DML2_ABS_PATH := $(patsubst %/,%,$(dir $(THIS_MAKEFILE_PATH))) + +DML2_C_FILES := $(shell find $(DML2_ABS_PATH) -name '*.c') # FPU flags step 2: Convert to .o and make paths relative to $(AMDDALPATH)/dc/dml2_0/ DML2_RELATIVE_O_FILES := $(patsubst $(DML2_ABS_PATH)/%,dc/dml2_0/%,$(patsubst %.c,%.o,$(DML2_C_FILES))) -- cgit v1.2.3 From 138531c8850cc247aa12b104bb29ea387bcdcbb1 Mon Sep 17 00:00:00 2001 From: Candice Li Date: Tue, 21 Jul 2026 21:38:58 +0800 Subject: drm/amdgpu: restore UMD profile pstate after runtime resume Runtime suspend runs GFX hw_fini and clears perfmon clock gating while the UMD profile DPM level remains set in software. Re-apply stable pstate after a successful runtime resume when a profile mode is active. Signed-off-by: Candice Li Reviewed-by: Hawking Zhang Reviewed-by: Yang Wang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 6c8ba5bd45ca..0ab380ca7e64 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -2920,6 +2920,19 @@ static int amdgpu_pmops_runtime_suspend(struct device *dev) return 0; } +static void amdgpu_restore_umd_profile_pstate_after_runpm(struct amdgpu_device *adev) +{ + enum amd_dpm_forced_level level; + uint32_t profile_mode_mask = AMD_DPM_FORCED_LEVEL_PROFILE_STANDARD | + AMD_DPM_FORCED_LEVEL_PROFILE_MIN_SCLK | + AMD_DPM_FORCED_LEVEL_PROFILE_MIN_MCLK | + AMD_DPM_FORCED_LEVEL_PROFILE_PEAK; + + level = amdgpu_dpm_get_performance_level(adev); + if (level & profile_mode_mask) + amdgpu_asic_update_umd_stable_pstate(adev, true); +} + static int amdgpu_pmops_runtime_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); @@ -2964,6 +2977,8 @@ static int amdgpu_pmops_runtime_resume(struct device *dev) if (adev->pm.rpm_mode == AMDGPU_RUNPM_PX) drm_dev->switch_power_state = DRM_SWITCH_POWER_ON; + + amdgpu_restore_umd_profile_pstate_after_runpm(adev); adev->in_runpm = false; return 0; } -- cgit v1.2.3 From 757ba0790bafec47a507e9662bf380f2e027d420 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Mon, 27 Jul 2026 12:23:17 +0800 Subject: drm/amd/pm: use milliwatts for GPU power sensors GPU average and input power backends report a mix of whole watts, milliwatts, Q24.8 watts and decimal-packed fractions. Q24.8 is inherited from the legacy PowerPlay sensor format. Milliwatts are a more natural unit for the hwmon and pm_info consumers in amdgpu_pm.c. A common decoder cannot distinguish these formats, and converting native milliwatts through Q24.8 also loses precision. Use milliwatts as the internal unit across all PPT and PowerPlay backends. Decode Q24.8 only at the legacy smu7 input boundary and encode it only for the raw amdgpu_sensors debugfs interface. This gives hwmon, pm_info and the sensor ioctl one unambiguous unit while preserving the format used by UMR. Fixes: 5b79d0482f3c ("drm/amd/pp: Remove struct pp_gpu_power") Fixes: 01992b121fb6 ("drm/amd/pm: fix amdgpu_pm_info power display units") Signed-off-by: Yang Wang Reviewed-by: Kenneth Feng Reported-by: Lars Nieradzik Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 9 +++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 4 ++-- drivers/gpu/drm/amd/include/kgd_pp_interface.h | 6 ++++-- drivers/gpu/drm/amd/pm/amdgpu_pm.c | 8 +++----- drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c | 4 ++-- drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c | 4 ++-- drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c | 2 +- drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c | 5 +++-- drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h | 5 +++++ drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c | 2 +- drivers/gpu/drm/amd/pm/swsmu/smu11/cyan_skillfish_ppt.c | 6 ++---- drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c | 8 ++++---- drivers/gpu/drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c | 7 ++++--- drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c | 9 +++------ drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c | 5 +++-- drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c | 3 ++- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 2 +- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c | 3 ++- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_4_ppt.c | 4 ++-- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_5_ppt.c | 2 +- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c | 3 ++- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c | 2 +- drivers/gpu/drm/amd/pm/swsmu/smu13/yellow_carp_ppt.c | 2 +- drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c | 3 +-- drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 2 +- drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c | 3 +-- drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c | 3 ++- 27 files changed, 65 insertions(+), 51 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index a0eadbb5772b..132d054900b5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -44,6 +44,11 @@ #if defined(CONFIG_DEBUG_FS) +/* Encode milliwatts in the raw Q24.8 sensor report format used by UMR. */ +#define AMDGPU_DEBUGFS_PWR_MW_TO_Q24_8(power_mw) \ + DIV_ROUND_CLOSEST_ULL((u64)(power_mw) * BIT(8), \ + MILLIWATT_PER_WATT) + /** * amdgpu_debugfs_process_reg_op - Handle MMIO register reads/writes * @@ -1105,6 +1110,10 @@ static ssize_t amdgpu_debugfs_sensor_read(struct file *f, char __user *buf, return r; } + if (idx == AMDGPU_PP_SENSOR_GPU_AVG_POWER || + idx == AMDGPU_PP_SENSOR_GPU_INPUT_POWER) + values[0] = AMDGPU_DEBUGFS_PWR_MW_TO_Q24_8(values[0]); + if (size > valuesize) { amdgpu_virt_disable_access_debugfs(adev); return -EINVAL; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 1e6ad59b74e0..242c48e85912 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -1211,7 +1211,7 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) return -EINVAL; } } - ui32 >>= 8; + ui32 /= MILLIWATT_PER_WATT; break; case AMDGPU_INFO_SENSOR_GPU_INPUT_POWER: /* get input GPU power */ @@ -1220,7 +1220,7 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) (void *)&ui32, &ui32_size)) { return -EINVAL; } - ui32 >>= 8; + ui32 /= MILLIWATT_PER_WATT; break; case AMDGPU_INFO_SENSOR_VDDNB: /* get VDDNB in millivolts */ diff --git a/drivers/gpu/drm/amd/include/kgd_pp_interface.h b/drivers/gpu/drm/amd/include/kgd_pp_interface.h index 6371f292f2d8..606d48115301 100644 --- a/drivers/gpu/drm/amd/include/kgd_pp_interface.h +++ b/drivers/gpu/drm/amd/include/kgd_pp_interface.h @@ -24,6 +24,8 @@ #ifndef __KGD_PP_INTERFACE_H__ #define __KGD_PP_INTERFACE_H__ +#include + extern const struct amdgpu_ip_block_version pp_smu_ip_block; extern const struct amdgpu_ip_block_version smu_v11_0_ip_block; extern const struct amdgpu_ip_block_version smu_v12_0_ip_block; @@ -150,8 +152,8 @@ enum amd_pp_sensors { AMDGPU_PP_SENSOR_MEM_TEMP, AMDGPU_PP_SENSOR_VCE_POWER, AMDGPU_PP_SENSOR_UVD_POWER, - AMDGPU_PP_SENSOR_GPU_AVG_POWER, - AMDGPU_PP_SENSOR_GPU_INPUT_POWER, + AMDGPU_PP_SENSOR_GPU_AVG_POWER, /* milliwatts */ + AMDGPU_PP_SENSOR_GPU_INPUT_POWER, /* milliwatts */ AMDGPU_PP_SENSOR_SS_APU_SHARE, AMDGPU_PP_SENSOR_SS_DGPU_SHARE, AMDGPU_PP_SENSOR_STABLE_PSTATE_SCLK, diff --git a/drivers/gpu/drm/amd/pm/amdgpu_pm.c b/drivers/gpu/drm/amd/pm/amdgpu_pm.c index e4fc33e3c951..4c6cdaeac5a9 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_pm.c @@ -41,8 +41,6 @@ #define DEVICE_ATTR_IS(_name) (attr_id == device_attr_id__##_name) -#define power_2_mwatt(power) (((power) >> 8) * 1000 + ((power) & 0xff)) - struct od_attribute { struct kobj_attribute attribute; struct list_head entry; @@ -3358,7 +3356,7 @@ static int amdgpu_hwmon_get_power(struct device *dev, return r; /* convert to microwatts */ - return power_2_mwatt(query) * 1000; + return query * 1000; } static ssize_t amdgpu_hwmon_show_power_avg(struct device *dev, @@ -4899,7 +4897,7 @@ static int amdgpu_debugfs_pm_info_pp(struct seq_file *m, struct amdgpu_device *a seq_printf(m, "\t%u mV (VDDNB)\n", value); size = sizeof(uint32_t); if (!amdgpu_dpm_read_sensor(adev, AMDGPU_PP_SENSOR_GPU_AVG_POWER, (void *)&query, &size)) { - mwatt = power_2_mwatt(query); + mwatt = query; centiwatt = DIV_ROUND_CLOSEST(mwatt, 10); if (adev->flags & AMD_IS_APU) seq_printf(m, "\t%u.%02u W (average SoC including CPU)\n", centiwatt / 100, centiwatt % 100); @@ -4908,7 +4906,7 @@ static int amdgpu_debugfs_pm_info_pp(struct seq_file *m, struct amdgpu_device *a } size = sizeof(uint32_t); if (!amdgpu_dpm_read_sensor(adev, AMDGPU_PP_SENSOR_GPU_INPUT_POWER, (void *)&query, &size)) { - mwatt = power_2_mwatt(query); + mwatt = query; centiwatt = DIV_ROUND_CLOSEST(mwatt, 10); if (adev->flags & AMD_IS_APU) seq_printf(m, "\t%u.%02u W (current SoC including CPU)\n", centiwatt / 100, centiwatt % 100); diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c index fbb59e5a4453..2393fa8d4e4b 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c @@ -4086,7 +4086,7 @@ static int smu7_get_gpu_power(struct pp_hwmgr *hwmgr, u32 *query) (adev->asic_type != CHIP_FIJI) && (adev->asic_type != CHIP_TONGA)) { smum_send_msg_to_smc_with_parameter(hwmgr, PPSMC_MSG_GetCurrPkgPwr, 0, &tmp); - *query = tmp; + *query = PP_PWR_Q24_8_TO_MW(tmp); if (tmp != 0) return 0; @@ -4105,7 +4105,7 @@ static int smu7_get_gpu_power(struct pp_hwmgr *hwmgr, u32 *query) if (tmp != 0) break; } - *query = tmp; + *query = PP_PWR_Q24_8_TO_MW(tmp); return 0; } diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c index 0e237feb1629..05b1e69116fd 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c @@ -3967,8 +3967,8 @@ static int vega10_get_gpu_power(struct pp_hwmgr *hwmgr, if (ret) return ret; - /* SMC returning actual watts, keep consistent with legacy asics, low 8 bit as 8 fractional bits */ - *query = value << 8; + /* SMC returns whole Watts, while power sensors use milliwatts. */ + *query = value * MILLIWATT_PER_WATT; return 0; } diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c index 69a9074058ce..b466b08e4a67 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega12_hwmgr.c @@ -1419,7 +1419,7 @@ static int vega12_get_gpu_power(struct pp_hwmgr *hwmgr, uint32_t *query) if (ret) return ret; - *query = metrics_table.CurrSocketPower << 8; + *query = metrics_table.CurrSocketPower * MILLIWATT_PER_WATT; return ret; } diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c index 7b8f4c1b80eb..e9c194056e32 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega20_hwmgr.c @@ -2154,12 +2154,13 @@ static int vega20_get_gpu_power(struct pp_hwmgr *hwmgr, int idx, switch (idx) { case AMDGPU_PP_SENSOR_GPU_AVG_POWER: if (hwmgr->smu_version == 0x282e00) - *query = metrics_table.AverageSocketPower << 8; + *query = metrics_table.AverageSocketPower * + MILLIWATT_PER_WATT; else ret = -EOPNOTSUPP; break; case AMDGPU_PP_SENSOR_GPU_INPUT_POWER: - *query = metrics_table.CurrSocketPower << 8; + *query = metrics_table.CurrSocketPower * MILLIWATT_PER_WATT; break; } diff --git a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h index e01afbc39d67..448246cc52cc 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h +++ b/drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h @@ -35,6 +35,11 @@ struct pp_hwmgr; struct phm_fan_speed_info; struct pp_atomctrl_voltage_table; +/* Decode legacy unsigned Q24.8 watts to internal milliwatts. */ +#define PP_PWR_Q24_8_TO_MW(power) \ + DIV_ROUND_CLOSEST_ULL((u64)(power) * MILLIWATT_PER_WATT, \ + BIT(8)) + #define VOLTAGE_SCALE 4 #define VOLTAGE_VID_OFFSET_SCALE1 625 #define VOLTAGE_VID_OFFSET_SCALE2 100 diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c index db5db2c9c8e8..e7628f4f1de4 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c @@ -658,7 +658,7 @@ static int arcturus_get_smu_metrics_data(struct smu_context *smu, *value = metrics->VcnActivityPercentage; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->TemperatureEdge * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/cyan_skillfish_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/cyan_skillfish_ppt.c index e6e009df9840..bb9620666dae 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/cyan_skillfish_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/cyan_skillfish_ppt.c @@ -158,12 +158,10 @@ cyan_skillfish_get_smu_metrics_data(struct smu_context *smu, *value = metrics->Current.MemclkFrequency; break; case METRICS_CURR_SOCKETPOWER: - *value = (metrics->Current.CurrentSocketPower << 8) / - 1000; + *value = metrics->Current.CurrentSocketPower; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = (metrics->Average.CurrentSocketPower << 8) / - 1000; + *value = metrics->Average.CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->Current.GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c index 8feea44f3ca0..9f8ab4e097dd 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c @@ -602,7 +602,7 @@ static int navi10_get_legacy_smu_metrics_data(struct smu_context *smu, *value = metrics->AverageUclkActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->TemperatureEdge * @@ -691,7 +691,7 @@ static int navi10_get_smu_metrics_data(struct smu_context *smu, *value = metrics->AverageUclkActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->TemperatureEdge * @@ -777,7 +777,7 @@ static int navi12_get_legacy_smu_metrics_data(struct smu_context *smu, *value = metrics->AverageUclkActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->TemperatureEdge * @@ -866,7 +866,7 @@ static int navi12_get_smu_metrics_data(struct smu_context *smu, *value = metrics->AverageUclkActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->TemperatureEdge * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c index c0de73b85353..cef6287524d4 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c @@ -855,9 +855,10 @@ static int sienna_cichlid_get_smu_metrics_data(struct smu_context *smu, metrics->AverageUclkActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = use_metrics_v3 ? metrics_v3->AverageSocketPower << 8 : - use_metrics_v2 ? metrics_v2->AverageSocketPower << 8 : - metrics->AverageSocketPower << 8; + *value = use_metrics_v3 ? metrics_v3->AverageSocketPower : + use_metrics_v2 ? metrics_v2->AverageSocketPower : + metrics->AverageSocketPower; + *value *= MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = (use_metrics_v3 ? metrics_v3->TemperatureEdge : diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c index c54675b00a96..3e3c68448ba9 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/vangogh_ppt.c @@ -318,8 +318,7 @@ static int vangogh_get_legacy_smu_metrics_data(struct smu_context *smu, *value = metrics->UvdActivity / 100; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = (metrics->CurrentSocketPower << 8) / - 1000 ; + *value = metrics->CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * @@ -387,12 +386,10 @@ static int vangogh_get_smu_metrics_data(struct smu_context *smu, *value = metrics->Current.UvdActivity; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = (metrics->Average.CurrentSocketPower << 8) / - 1000; + *value = metrics->Average.CurrentSocketPower; break; case METRICS_CURR_SOCKETPOWER: - *value = (metrics->Current.CurrentSocketPower << 8) / - 1000; + *value = metrics->Current.CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->Current.GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c index 2b011610e3c7..0ec17c1054c0 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c @@ -1215,9 +1215,10 @@ static int renoir_get_smu_metrics_data(struct smu_context *smu, ((amdgpu_ip_version(adev, MP1_HWIP, 0) == IP_VERSION(12, 0, 0)) && (adev->pm.fw_version >= 0x373200))) - *value = metrics->CurrentSocketPower << 8; + *value = metrics->CurrentSocketPower * + MILLIWATT_PER_WATT; else - *value = (metrics->CurrentSocketPower << 8) / 1000; + *value = metrics->CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = (metrics->GfxTemperature / 100) * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c index cd7bf36673cb..cbdfde79cb9e 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c @@ -677,7 +677,8 @@ static int aldebaran_get_smu_metrics_data(struct smu_context *smu, case METRICS_AVERAGE_SOCKETPOWER: /* Valid power data is available only from primary die */ if (aldebaran_is_primary(smu)) - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * + MILLIWATT_PER_WATT; else ret = -EOPNOTSUPP; break; diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c index 4ce1429cf57b..48e54881a5d0 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c @@ -807,7 +807,7 @@ static int smu_v13_0_0_get_smu_metrics_data(struct smu_context *smu, metrics->Vcn1ActivityPercentage); break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->AvgTemperature[TEMP_EDGE] * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c index ecdbf9bb8eb7..8c719fff754a 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c @@ -434,7 +434,8 @@ int smu_v13_0_12_get_smu_metrics_data(struct smu_context *smu, *value = SMUQ10_ROUND(metrics->DramBandwidthUtilization); break; case METRICS_CURR_SOCKETPOWER: - *value = SMUQ10_ROUND(metrics->SocketPower) << 8; + *value = SMUQ10_ROUND(metrics->SocketPower) * + MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_HOTSPOT: *value = SMUQ10_ROUND(metrics->MaxSocketTemperature) * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_4_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_4_ppt.c index ba91bf590eed..a9d558f5b739 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_4_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_4_ppt.c @@ -338,10 +338,10 @@ static int smu_v13_0_4_get_smu_metrics_data(struct smu_context *smu, *value = metrics->UvdActivity / 100; break; case METRICS_AVERAGE_SOCKETPOWER: - *value = (metrics->AverageSocketPower << 8) / 1000; + *value = metrics->AverageSocketPower; break; case METRICS_CURR_SOCKETPOWER: - *value = (metrics->CurrentSocketPower << 8) / 1000; + *value = metrics->CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_5_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_5_ppt.c index 27372a60e83d..f95b15af436d 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_5_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_5_ppt.c @@ -295,7 +295,7 @@ static int smu_v13_0_5_get_smu_metrics_data(struct smu_context *smu, *value = metrics->UvdActivity / 100; break; case METRICS_CURR_SOCKETPOWER: - *value = (metrics->CurrentSocketPower << 8) / 1000; + *value = metrics->CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c index 64db62a8d642..db94de88add2 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c @@ -1300,7 +1300,8 @@ static int smu_v13_0_6_get_smu_metrics_data(struct smu_context *smu, *value = SMUQ10_ROUND(GET_METRIC_FIELD(DramBandwidthUtilization, version)); break; case METRICS_CURR_SOCKETPOWER: - *value = SMUQ10_ROUND(GET_METRIC_FIELD(SocketPower, version)) << 8; + *value = SMUQ10_ROUND(GET_METRIC_FIELD(SocketPower, version)) * + MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_HOTSPOT: *value = SMUQ10_ROUND(GET_METRIC_FIELD(MaxSocketTemperature, version)) * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c index 5f23f2e7f401..4deb987e43e0 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c @@ -817,7 +817,7 @@ static int smu_v13_0_7_get_smu_metrics_data(struct smu_context *smu, *value = smu_safe_u16_nn(metrics->AverageUclkActivity); break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->AvgTemperature[TEMP_EDGE] * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/yellow_carp_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/yellow_carp_ppt.c index 7bf88ffd311b..edc3fedca9f8 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/yellow_carp_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/yellow_carp_ppt.c @@ -372,7 +372,7 @@ static int yellow_carp_get_smu_metrics_data(struct smu_context *smu, *value = metrics->UvdActivity / 100; break; case METRICS_CURR_SOCKETPOWER: - *value = (metrics->CurrentSocketPower << 8) / 1000; + *value = metrics->CurrentSocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c index 3d73f2050bbe..568665265a13 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c @@ -312,8 +312,7 @@ static int smu_v14_0_0_get_smu_metrics_data(struct smu_context *smu, break; case METRICS_AVERAGE_SOCKETPOWER: case METRICS_CURR_SOCKETPOWER: - *value = (metrics->SocketPower / 1000 << 8) + - (metrics->SocketPower % 1000 / 10); + *value = metrics->SocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c index 0fbcaf884419..2fa4ce26e950 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c @@ -695,7 +695,7 @@ static int smu_v14_0_2_get_smu_metrics_data(struct smu_context *smu, metrics->Vcn1ActivityPercentage); break; case METRICS_AVERAGE_SOCKETPOWER: - *value = metrics->AverageSocketPower << 8; + *value = metrics->AverageSocketPower * MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->AvgTemperature[TEMP_EDGE] * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c index faa7d661f3bf..0da1ffb3a567 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_0_ppt.c @@ -378,8 +378,7 @@ static int smu_v15_0_0_get_smu_metrics_data(struct smu_context *smu, break; case METRICS_AVERAGE_SOCKETPOWER: case METRICS_CURR_SOCKETPOWER: - *value = (metrics->SocketPower / 1000 << 8) + - (metrics->SocketPower % 1000 / 10); + *value = metrics->SocketPower; break; case METRICS_TEMPERATURE_EDGE: *value = metrics->GfxTemperature / 100 * diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c index 227cd86e13e8..59fdd2319b66 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c @@ -411,7 +411,8 @@ static int smu_v15_0_8_get_smu_metrics_data(struct smu_context *smu, *value = SMUQ10_ROUND(metrics->DramBandwidthUtilization); break; case METRICS_CURR_SOCKETPOWER: - *value = SMUQ10_ROUND(metrics->SocketPower) << 8; + *value = SMUQ10_ROUND(metrics->SocketPower) * + MILLIWATT_PER_WATT; break; case METRICS_TEMPERATURE_HOTSPOT: *value = SMUQ10_ROUND(metrics->MaxSocketTemperature) * -- cgit v1.2.3 From 5dafdd649280c7dc6c22c8f877da3f54fcc441e1 Mon Sep 17 00:00:00 2001 From: Harkirat Gill Date: Mon, 27 Jul 2026 14:37:56 -0400 Subject: drm/amdgpu: cap GTT size to physical RAM on APUs On APUs, the GTT pool is backed by system RAM, but its size is not bound to the non-carveout memory that actually backs it. A user can end up with GTT + VRAM exceeding total physical memory through the following sequence: - Have a large non-carveout memory space (~128GB) and accordingly set a large GTT (~100GB) via the ttm module parameter. - Lower the non-carveout memory space in BIOS by increasing the UMA Frame Buffer Size (VRAM) to 64GB. - The previously set GTT value (~100GB) persists, even though the new non-carveout space (64GB) can no longer back it. This leads to a case where kernel reports GTT (100GB) + VRAM (64GB) despite the sum being greater than total physical memory (128GB). Cap the GTT size to totalram_pages() on APUs. totalram_pages() already excludes the VRAM carveout, so the resulting GTT can never exceed the system RAM that actually backs it. Signed-off-by: Harkirat Gill Reviewed-by: David Francis Assisted-by: Claude:claude-opus-4 Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 5fe29e4972d8..5f97a11eef7a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2174,6 +2174,18 @@ int amdgpu_ttm_init(struct amdgpu_device *adev) gtt_size = configured_size; } + /* Cap GTT so that it does not exceed total physical RAM. */ + if (adev->flags & AMD_IS_APU) { + u64 phys_ram = (u64)totalram_pages() << PAGE_SHIFT; + + if (gtt_size > phys_ram) { + gtt_size = phys_ram; + dev_info(adev->dev, + "Capping GTT to %uM to not exceed available system memory\n", + (unsigned int)(gtt_size / (1024 * 1024))); + } + } + /* Initialize GTT memory pool */ r = amdgpu_gtt_mgr_init(adev, gtt_size); if (r) { -- cgit v1.2.3 From b38f9b6ffdfc86d5aff36881d9e8028c79bc98c9 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Tue, 28 Jul 2026 14:08:20 -0400 Subject: drm/amdkfd: Fix signal reset event When a mode reset happens, driver needs to notify the process on that GPU a reset event is happening. The existing code assumes the process is using the GPU that is getting mode reset, which is not always true. For example, on a 8G system, the process may be only using GPU 0~4 but a mode 2 reset is resetting the all 8 GPUs connected by XGMI. Trying to find a process on GPU 5~7 will fail, which is fine and should skip the event signal. Signed-off-by: Amber Lin Reviewed-by: David Yat Sin Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_events.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_events.c index 5fa156e89a73..6e32a892d56f 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_events.c @@ -1164,6 +1164,8 @@ void kfd_signal_reset_event(struct kfd_node *dev) struct kfd_event *ev; unsigned int temp; uint32_t id, idx; + int user_gpu_id; + struct kfd_process_device *pdd; int reset_cause = atomic_read(&dev->sram_ecc_flag) ? KFD_HW_EXCEPTION_ECC : KFD_HW_EXCEPTION_GPU_HANG; @@ -1179,17 +1181,14 @@ void kfd_signal_reset_event(struct kfd_node *dev) idx = srcu_read_lock(&kfd_processes_srcu); hash_for_each_rcu(kfd_processes_table, temp, p, kfd_processes) { - int user_gpu_id = kfd_process_get_user_gpu_id(p, dev->id); - struct kfd_process_device *pdd = kfd_get_process_device_data(dev, p); - - if (unlikely(user_gpu_id == -EINVAL)) { - WARN_ONCE(1, "Could not get user_gpu_id from dev->id:%x\n", dev->id); + pdd = kfd_get_process_device_data(dev, p); + if (!pdd) + /* no process is using this device */ continue; - } + user_gpu_id = kfd_process_get_user_gpu_id(p, dev->id); - if (unlikely(!pdd)) { - WARN_ONCE(1, "Could not get device data from process pid:%d\n", - p->lead_thread->pid); + if (unlikely(user_gpu_id == -EINVAL)) { + WARN_ONCE(1, "Could not get user_gpu_id from dev->id:%d\n", dev->id); continue; } -- cgit v1.2.3 From 25f0b322ffb5ba1d61175ad6b0c53bf0bcf3d301 Mon Sep 17 00:00:00 2001 From: Philip Yang Date: Thu, 16 Jul 2026 17:39:34 -0400 Subject: drm/amdkfd: Evict SVM BOs synchronously from TTM eviction svm_range_evict_svm_bo_worker() migrated an SVM BO's pages back to system memory from a work item that took mmap_read_lock. When an mmap writer was pending, that read lock blocked behind the writer while the thread allocating a new migration VRAM BO waited on this BO's eviction fence - a circular wait that hung the SVM workers. Evict the SVM BO synchronously from the TTM eviction path (amdgpu_ttm_bo_eviction_valuable) instead of deferring to a work item. The BO is already reserved and the lock order is mmap_lock -> BO reservation, so only trylock the owning process's mmap lock; on contention return -EBUSY so TTM skips this BO. This removes the eviction work item and the enable_signaling path, so no worker can block on mmap_read_lock. The SVM BO uses AMDGPU_GEM_CREATE_DISCARDABLE, so ttm_bo_evict takes the pipeline_gutting path and skips allocating a system memory placement. That would be wasted work, since svm_migrate_vram_to_ram allocates the system pages and copies the data back itself. Eviction now migrates ranges directly, so it must serialize with the owning process: it trylocks migrate_mutex under svm_bo->list_lock before unlinking the range, and svm_range_free() unlinks the range then waits on migrate_mutex, so a concurrent eviction cannot free a range under it. Drop the mm reference with mmput_async so exit_mmap() does not run under the BO reservation. Signed-off-by: Philip Yang Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 22 ++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c | 3 - drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 16 +++++ drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 88 +++++++++++++++--------- drivers/gpu/drm/amd/amdkfd/kfd_svm.h | 10 ++- 6 files changed, 100 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index 121282dd30c1..be764b6802b5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -39,6 +39,7 @@ #if IS_ENABLED(CONFIG_HSA_AMD) #include "kfd_priv.h" #endif +#include "kfd_svm.h" /* Total memory size in system memory and all GPU VRAM. Used to * estimate worst case amount of memory to reserve for page tables @@ -972,3 +973,24 @@ int amdgpu_amdkfd_reset_mes_queue(struct amdgpu_device *adev, return kgd2kfd_reset_mes_queue(adev->kfd.dev, node_id, queue_type, pipe, queue, db); } + +int amdgpu_amdkfd_evict_svm_bo(struct amdgpu_bo *bo) +{ + struct dma_resv_iter cursor; + struct dma_fence *fence; + int r = 0; + + dma_resv_iter_begin(&cursor, bo->tbo.base.resv, DMA_RESV_USAGE_BOOKKEEP); + dma_resv_for_each_fence_unlocked(&cursor, fence) { + struct amdgpu_amdkfd_fence *f = to_amdgpu_amdkfd_fence(fence); + + if (f && f->svm_bo) { + r = svm_range_evict_svm_bo(f->svm_bo); + if (r) + break; + } + } + dma_resv_iter_end(&cursor); + + return r; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h index 338412a750ed..b4840ee36f2b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h @@ -286,6 +286,7 @@ int amdgpu_amdkfd_reset_mes_queue(struct amdgpu_device *adev, int queue_type, int pipe, int queue, unsigned int db); +int amdgpu_amdkfd_evict_svm_bo(struct amdgpu_bo *bo); /* Read user wptr from a specified user address space with page fault * disabled. The memory must be pinned and mapped to the hardware when diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c index b0299d861903..553d26c2744e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_fence.c @@ -135,9 +135,6 @@ static bool amdkfd_fence_enable_signaling(struct dma_fence *f) if (!fence->svm_bo) { if (!kgd2kfd_schedule_evict_and_restore_process(fence->mm, fence->context_id, f)) return true; - } else { - if (!svm_range_schedule_evict_svm_bo(fence)) - return true; } return false; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 5f97a11eef7a..91b7bf6e65f5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -1488,6 +1488,7 @@ static bool amdgpu_ttm_bo_eviction_valuable(struct ttm_buffer_object *bo, const struct ttm_place *place) { struct dma_resv_iter resv_cursor; + struct amdgpu_bo *abo; struct dma_fence *f; if (!amdgpu_bo_is_amdgpu_bo(bo)) @@ -1497,6 +1498,21 @@ static bool amdgpu_ttm_bo_eviction_valuable(struct ttm_buffer_object *bo, if (bo->resource->mem_type == TTM_PL_SYSTEM) return true; + abo = ttm_to_amdgpu_bo(bo); + if (abo->flags & AMDGPU_GEM_CREATE_DISCARDABLE) { + /* + * SVM BOs are migrated to system memory synchronously in this + * TTM eviction context. The migration needs the owning + * process's mmap lock, but the normal lock order is + * mmap_lock -> BO reservation and the BO is already reserved + * here. svm_range_evict_svm_bo() only trylocks the mmap lock; + * if the eviction fails for any reason, we return false so TTM + * skips this BO instead of risking a deadlock. + */ + if (amdgpu_amdkfd_evict_svm_bo(abo) < 0) + return false; + } + if (bo->type == ttm_bo_type_kernel && !amdgpu_vm_evictable(ttm_to_amdgpu_bo(bo))) return false; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index 30ad10bbd47e..4c6700c6e88d 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -69,7 +69,6 @@ struct criu_svm_metadata { struct kfd_criu_svm_range_priv_data data; }; -static void svm_range_evict_svm_bo_worker(struct work_struct *work); static bool svm_range_cpu_invalidate_pagetables(struct mmu_interval_notifier *mni, const struct mmu_notifier_range *range, @@ -97,7 +96,7 @@ static void svm_range_unlink(struct svm_range *prange) if (prange->svm_bo) { spin_lock(&prange->svm_bo->list_lock); - list_del(&prange->svm_bo_list); + list_del_init(&prange->svm_bo_list); spin_unlock(&prange->svm_bo->list_lock); } @@ -286,6 +285,17 @@ static void svm_range_free(struct svm_range *prange, bool do_unmap) pr_debug("svms 0x%p prange 0x%p [0x%lx 0x%lx]\n", prange->svms, prange, prange->start, prange->last); + /* Unlink from range_list; no-op if already unlinked. */ + if (prange->svm_bo) { + spin_lock(&prange->svm_bo->list_lock); + list_del_init(&prange->svm_bo_list); + spin_unlock(&prange->svm_bo->list_lock); + } + + /* Wait for any in-flight eviction of this range to finish. */ + mutex_lock(&prange->migrate_mutex); + mutex_unlock(&prange->migrate_mutex); + svm_range_vram_node_free(prange); if (do_unmap) svm_range_dma_unmap(prange); @@ -588,7 +598,6 @@ svm_range_vram_node_new(struct kfd_node *node, struct svm_range *prange, mm, svm_bo, p->context_id); mmput(mm); - INIT_WORK(&svm_bo->eviction_work, svm_range_evict_svm_bo_worker); svm_bo->evicting = 0; memset(&bp, 0, sizeof(bp)); bp.size = prange->npages * PAGE_SIZE; @@ -3631,39 +3640,36 @@ svm_range_trigger_migration(struct mm_struct *mm, struct svm_range *prange, return 0; } -int svm_range_schedule_evict_svm_bo(struct amdgpu_amdkfd_fence *fence) -{ - /* Dereferencing fence->svm_bo is safe here because the fence hasn't - * signaled yet and we're under the protection of the fence->lock. - * After the fence is signaled in svm_range_bo_release, we cannot get - * here any more. - * - * Reference is dropped in svm_range_evict_svm_bo_worker. - */ - if (svm_bo_ref_unless_zero(fence->svm_bo)) { - WRITE_ONCE(fence->svm_bo->evicting, 1); - schedule_work(&fence->svm_bo->eviction_work); - } - - return 0; -} - -static void svm_range_evict_svm_bo_worker(struct work_struct *work) +int svm_range_evict_svm_bo(struct svm_range_bo *svm_bo) { - struct svm_range_bo *svm_bo; struct mm_struct *mm; int r = 0; - svm_bo = container_of(work, struct svm_range_bo, eviction_work); + if (!svm_bo_ref_unless_zero(svm_bo)) + return 0; - if (mmget_not_zero(svm_bo->eviction_fence->mm)) { - mm = svm_bo->eviction_fence->mm; - } else { + if (!mmget_not_zero(svm_bo->eviction_fence->mm)) { svm_range_bo_unref(svm_bo); - return; + return 0; } + mm = svm_bo->eviction_fence->mm; + + /* + * Called with the BO reserved; lock order is mmap_lock -> BO + * reservation. Only trylock mmap to invert that order safely: a + * trylock never blocks, so it cannot deadlock against the reservation + * and lockdep records no reverse dependency. On contention return + * -EBUSY so TTM skips this BO. + */ + if (!mmap_read_trylock(mm)) { + pr_debug("skip eviction, contended to take mmap_read lock\n"); + mmput_async(mm); + svm_range_bo_unref(svm_bo); + return -EBUSY; + } + + WRITE_ONCE(svm_bo->evicting, 1); - mmap_read_lock(mm); spin_lock(&svm_bo->list_lock); while (!list_empty(&svm_bo->range_list) && !r) { struct svm_range *prange = @@ -3671,13 +3677,24 @@ static void svm_range_evict_svm_bo_worker(struct work_struct *work) struct svm_range, svm_bo_list); int retries = 3; + /* + * Trylock migrate_mutex under list_lock, before unlinking the + * range, so svm_range_free() cannot free it under us. On + * contention the owner is migrating this range; skip the BO. + */ + if (!mutex_trylock(&prange->migrate_mutex)) { + pr_debug("skip eviction, contended migrate_mutex\n"); + /* Clear evicting so the BO keeps being reused. */ + WRITE_ONCE(svm_bo->evicting, 0); + r = -EBUSY; + break; + } list_del_init(&prange->svm_bo_list); spin_unlock(&svm_bo->list_lock); pr_debug("svms 0x%p [0x%lx 0x%lx]\n", prange->svms, prange->start, prange->last); - mutex_lock(&prange->migrate_mutex); do { /* migrate all vram pages in this prange to sys ram * after that prange->actual_loc should be zero @@ -3701,15 +3718,24 @@ static void svm_range_evict_svm_bo_worker(struct work_struct *work) } spin_unlock(&svm_bo->list_lock); mmap_read_unlock(mm); - mmput(mm); + /* Defer mmput: exit_mmap() must not run under the BO reservation. */ + mmput_async(mm); - dma_fence_signal(&svm_bo->eviction_fence->base); + /* + * Only signal the eviction fence once the ranges have been processed. + * On -EBUSY we bailed out without migrating; leave the BO in VRAM and + * let TTM retry later. + */ + if (r != -EBUSY) + dma_fence_signal(&svm_bo->eviction_fence->base); /* This is the last reference to svm_bo, after svm_range_vram_node_free * has been called in svm_migrate_vram_to_ram */ WARN_ONCE(!r && kref_read(&svm_bo->kref) != 1, "This was not the last reference\n"); svm_range_bo_unref(svm_bo); + + return r; } static int diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.h b/drivers/gpu/drm/amd/amdkfd/kfd_svm.h index a63dfc95b602..4232c47422e6 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.h @@ -44,7 +44,6 @@ struct svm_range_bo { struct list_head range_list; /* all svm ranges shared this bo */ spinlock_t list_lock; struct amdgpu_amdkfd_fence *eviction_fence; - struct work_struct eviction_work; uint32_t evicting; struct work_struct release_work; struct kfd_node *node; @@ -175,7 +174,8 @@ void svm_range_vram_node_free(struct svm_range *prange); int svm_range_restore_pages(struct amdgpu_device *adev, unsigned int pasid, uint32_t vmid, uint32_t node_id, uint64_t addr, uint64_t ts, bool write_fault); -int svm_range_schedule_evict_svm_bo(struct amdgpu_amdkfd_fence *fence); +int svm_range_evict_svm_bo(struct svm_range_bo *svm_bo); + void svm_range_add_list_work(struct svm_range_list *svms, struct svm_range *prange, struct mm_struct *mm, enum svm_work_list_ops op); @@ -229,11 +229,9 @@ static inline int svm_range_restore_pages(struct amdgpu_device *adev, return -EFAULT; } -static inline int svm_range_schedule_evict_svm_bo( - struct amdgpu_amdkfd_fence *fence) +static inline int svm_range_evict_svm_bo(struct svm_range_bo *svm_bo) { - WARN_ONCE(1, "SVM eviction fence triggered, but SVM is disabled"); - return -EINVAL; + return 0; } static inline void svm_range_get_info(struct kfd_process *p, -- cgit v1.2.3 From 6f16fcbb0c46a87e3d9685407e906573d60104b0 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Thu, 23 Jul 2026 06:25:48 +0200 Subject: drm/amd/display: use proper context for logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same as the rest of the code, get_ss_info_from_atombios() uses calc_pll_cs->ctx->logger for logging. But calc_pll_cs->ctx is initialized only later in calc_pll_max_vco_construct(). Therefore, any output using DC_LOG_SYNC() leads to a NULL pointer deference in get_ss_info_from_atombios(). According to Sashiko, the very same problem exists in dce112_get_pix_clk_dividers() and dcn3_get_pix_clk_dividers() too. To avoid accessing the NULL context, use clk_src->base.ctx->logger everywhere. That context in base is initialized earlier in dce110_clk_src_construct() and dce112_clk_src_construct(). Before get_ss_info_from_atombios() or Sashiko's get_pix_clk_dividers functions above are actually called. This is done by redefining DC_LOGGER to CTX->logger. Before: dce110_clk_src_construct() did: -> sets clk_src->base.ctx = ctx; -> ss_info_from_atombios_create() -> get_ss_info_from_atombios() <- uses calc_pll_cs->ctx # BOOM -> calc_pll_max_vco_construct() <- sets calc_pll_cs->ctx After: dce110_clk_src_construct() does: -> sets clk_src->base.ctx = ctx; -> ss_info_from_atombios_create() -> get_ss_info_from_atombios() <- uses clk_src->base.ctx Closes: https://bugzilla.suse.com/show_bug.cgi?id=1271175 Closes: https://lore.kernel.org/all/a9ee54e6-2413-4156-9bde-d528ae3c63a3@kernel.org/ Fixes: 1296423bf23c ("drm/amd/display: define DC_LOGGER for logger") Reviewed-by: Bhawanpreet Lakha Signed-off-by: Jiri Slaby (SUSE) Cc: Lakha, Bhawanpreet Cc: Harry Wentland Cc: Leo Li Cc: Rodrigo Siqueira Cc: Alex Deucher Cc: "Christian König" Cc: David Airlie Cc: Simona Vetter Cc: amd-gfx@lists.freedesktop.org Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/dce/dce_clock_source.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c b/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c index ecb8493ec523..8e3862f6dac2 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c @@ -45,9 +45,7 @@ clk_src->base.ctx #define DC_LOGGER \ - calc_pll_cs->ctx->logger -#define DC_LOGGER_INIT() \ - struct calc_pll_clock_source *calc_pll_cs = &clk_src->calc_pll + CTX->logger #undef FN #define FN(reg_name, field_name) \ @@ -287,6 +285,7 @@ static bool calc_pll_dividers_in_range( } static uint32_t calculate_pixel_clock_pll_dividers( + struct dce110_clk_src *clk_src, struct calc_pll_clock_source *calc_pll_cs, struct pll_settings *pll_settings) { @@ -474,7 +473,7 @@ static uint32_t dce110_get_pix_clk_dividers_helper ( { uint32_t field = 0; uint32_t pll_calc_error = MAX_PLL_CALC_ERROR; - DC_LOGGER_INIT(); + /* Check if reference clock is external (not pcie/xtalin) * HW Dce80 spec: * 00 - PCIE_REFCLK, 01 - XTALIN, 02 - GENERICA, 03 - GENERICB @@ -517,12 +516,14 @@ static uint32_t dce110_get_pix_clk_dividers_helper ( /*Calculate Dividers by HDMI object, no SS case or SS case */ pll_calc_error = calculate_pixel_clock_pll_dividers( + clk_src, &clk_src->calc_pll_hdmi, pll_settings); else /*Calculate Dividers by default object, no SS case or SS case */ pll_calc_error = calculate_pixel_clock_pll_dividers( + clk_src, &clk_src->calc_pll, pll_settings); @@ -568,7 +569,6 @@ static uint32_t dce110_get_pix_clk_dividers( { struct dce110_clk_src *clk_src = TO_DCE110_CLK_SRC(cs); uint32_t pll_calc_error = MAX_PLL_CALC_ERROR; - DC_LOGGER_INIT(); if (pix_clk_params == NULL || pll_settings == NULL || pix_clk_params->requested_pix_clk_100hz == 0) { @@ -600,7 +600,6 @@ static uint32_t dce112_get_pix_clk_dividers( struct pll_settings *pll_settings) { struct dce110_clk_src *clk_src = TO_DCE110_CLK_SRC(cs); - DC_LOGGER_INIT(); if (pix_clk_params == NULL || pll_settings == NULL || pix_clk_params->requested_pix_clk_100hz == 0) { @@ -1442,8 +1441,6 @@ static uint32_t dcn3_get_pix_clk_dividers( unsigned long long actual_pix_clk_100Hz = pix_clk_params ? pix_clk_params->requested_pix_clk_100hz : 0; struct dce110_clk_src *clk_src = TO_DCE110_CLK_SRC(cs); - DC_LOGGER_INIT(); - if (pix_clk_params == NULL || pll_settings == NULL || pix_clk_params->requested_pix_clk_100hz == 0) { DC_LOG_ERROR( @@ -1513,7 +1510,6 @@ static const struct clock_source_funcs dce110_clk_src_funcs = { .get_dp_dto_frequency_100hz = get_dp_dto_frequency_100hz }; - static void get_ss_info_from_atombios( struct dce110_clk_src *clk_src, enum as_signal_type as_signal, @@ -1526,7 +1522,7 @@ static void get_ss_info_from_atombios( struct spread_spectrum_info *ss_info_cur; struct spread_spectrum_data *ss_data_cur; uint32_t i; - DC_LOGGER_INIT(); + if (ss_entries_num == NULL) { DC_LOG_SYNC( "Invalid entry !!!\n"); @@ -1657,6 +1653,7 @@ static void ss_info_from_atombios_create( } static bool calc_pll_max_vco_construct( + struct dce110_clk_src *clk_src, struct calc_pll_clock_source *calc_pll_cs, struct calc_pll_clock_source_init_data *init_data) { @@ -1808,6 +1805,7 @@ bool dce110_clk_src_construct( ss_info_from_atombios_create(clk_src); if (!calc_pll_max_vco_construct( + clk_src, &clk_src->calc_pll, &calc_pll_cs_init_data)) { ASSERT_CRITICAL(false); @@ -1822,7 +1820,7 @@ bool dce110_clk_src_construct( if (!calc_pll_max_vco_construct( - &clk_src->calc_pll_hdmi, &calc_pll_cs_init_data_hdmi)) { + clk_src, &clk_src->calc_pll_hdmi, &calc_pll_cs_init_data_hdmi)) { ASSERT_CRITICAL(false); goto unexpected_failure; } -- cgit v1.2.3